vue3导出word

Posted by Shallow Dreameron October 27, 2024

要将页面中的图片 DOM 元素和表格按顺序导出到 Word 文档中,我们可以在 contentArray 中添加一个标识,来处理从 DOM 中获取的图片。这是更新后的通用组件。

更新的通用组件

<template>
    <div>
        <button @click="exportToWord">导出 Word 文档</button>
    </div>
</template>

<script>
import { Document, Packer, Paragraph, Table, TableRow, TableCell, WidthType, ImageRun } from 'docx';
import { saveAs } from 'file-saver';

export default {
    props: {
        contentArray: {
            type: Array,
            required: true, // 接收一个包含表格和图片的数组
        },
        title: {
            type: String,
            default: '导出的文档',
        },
    },
    methods: {
        async exportToWord() {
            const doc = new Document();

            // 添加标题
            doc.addSection({
                children: [
                    new Paragraph({
                        text: this.title,
                        heading: 'Heading1',
                    }),
                ],
            });

            // 遍历 contentArray,添加表格和图片
            for (const item of this.contentArray) {
                if (item.type === 'table') {
                    this.addTableToDoc(doc, item.data);
                } else if (item.type === 'image') {
                    await this.addImageToDoc(doc, item.src);
                } else if (item.type === 'dom-image') {
                    await this.addDomImageToDoc(doc, item.domElementId);
                }
            }

            // 导出文档
            Packer.toBlob(doc).then(blob => {
                saveAs(blob, `${this.title}.docx`);
            });
        },
        addTableToDoc(doc, tableData) {
            const rows = tableData.map(row => {
                return new TableRow({
                    children: row.map(cell => new TableCell({ children: [new Paragraph(cell)] })),
                });
            });

            const table = new Table({
                rows,
                width: { size: 100, type: WidthType.PERCENTAGE },
            });

            doc.addSection({
                children: [table],
            });
        },
        async addImageToDoc(doc, imageUrl) {
            const imageBuffer = await fetch(imageUrl).then(res => res.arrayBuffer());
            const image = new ImageRun({
                data: imageBuffer,
                transformation: {
                    width: 200, // 图片宽度
                    height: 100, // 图片高度
                },
            });

            doc.addSection({
                children: [new Paragraph(image)],
            });
        },
        async addDomImageToDoc(doc, domElementId) {
            const domElement = document.getElementById(domElementId);
            if (domElement) {
                const images = domElement.getElementsByTagName('img');
                for (const img of images) {
                    await this.addImageToDoc(doc, img.src);
                }
            }
        },
    },
};
</script>

<style scoped>
button {
    padding: 10px;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}
</style>

使用示例

在父组件中,你可以这样使用这个组件:

<template>
    <div>
        <ExportWord
            :contentArray="contentArray"
            title="我的文档"
        />
        
        <div id="imageContainer" style="display: none;">
            <img src="https://example.com/image1.png" alt="Image 1" />
            <img src="https://example.com/image2.png" alt="Image 2" />
        </div>
    </div>
</template>

<script>
import ExportWord from './ExportWord.vue';

export default {
    components: {
        ExportWord,
    },
    data() {
        return {
            contentArray: [
                {
                    type: 'table',
                    data: [
                        ['列1', '列2'],
                        ['数据1', '数据2'],
                    ],
                },
                {
                    type: 'image',
                    src: 'https://example.com/image3.png',
                },
                {
                    type: 'dom-image',
                    domElementId: 'imageContainer', // 指定包含图片的 DOM 元素 ID
                },
                {
                    type: 'table',
                    data: [
                        ['列A', '列B'],
                        ['数据A', '数据B'],
                    ],
                },
            ],
        };
    },
};
</script>

说明

  • contentArray: 数组中的每个对象可以是:
    • type: 'table': 表示一个表格,data 是表格的二维数组。
    • type: 'image': 表示单个图片,src 是图片的 URL。
    • type: 'dom-image': 表示从页面 DOM 中获取的图片,domElementId 是包含图片的 DOM 元素 ID。

这样,你可以灵活地添加多个表格、图片和从 DOM 中提取的图片,并按所需顺序导出到 Word 文档中!


// loading状态管理 - 使用Vue3 响应式API
import { ref } from 'vue'
import { ElLoading } from 'element-plus'

class LoadingManager {
    constructor() {
        this.loadingInstance = null;
        this.loadingText = ref('加载中...');
    }

    // 显示loading
    showLoading(text) {
        if(text) {
            this.loadingText.value = text;
        }
        this.loadingInstance = ElLoading.service({
            lock: true,
            text: this.loadingText.value,
            background: 'rgba(0, 0, 0, 0.7)'
        });
    }

    // 更新loading文本
    updateLoadingText(text) {
        if(this.loadingInstance) {
            this.loadingText.value = text;
            this.loadingInstance.setText(text);
        }
    }

    // 关闭loading
    closeLoading() {
        if(this.loadingInstance) {
            this.loadingInstance.close();
            this.loadingInstance = null;
        }
    }
}

// 创建loading管理实例
const loadingManager = new LoadingManager();

// 全局路由守卫中使用
router.beforeEach(async (to, from, next) => {
    // 白名单路由直接放行
    const whiteList = ['/login', '/register', '/404'];
    if (whiteList.includes(to.path)) {
        next();
        return;
    }

    try {
        loadingManager.showLoading('验证登录中...');
        
        // 验证登录请求
        const response = await fetch('/api/verify-login', {
            method: 'POST',
            credentials: 'include'
        });
        
        if (!response.ok) {
            throw new Error('登录验证失败');
        }
        
        const result = await response.json();
        
        // 可以根据需要更新loading文本
        loadingManager.updateLoadingText('验证成功,正在跳转...');
        
        if (result.isLoggedIn) {
            next();
        } else {
            ElMessage.error('请先登录');
            next('/login');
        }
        
    } catch (error) {
        console.error('登录验证出错:', error);
        ElMessage.error(error.message);
        next('/login');
    } finally {
        loadingManager.closeLoading();
    }
});

// 在Vue组件中使用示例:
const useLoading = () => {
    const showPageLoading = (text) => {
        loadingManager.showLoading(text);
    };
    
    const updateLoadingText = (text) => {
        loadingManager.updateLoadingText(text);
    };
    
    const closePageLoading = () => {
        loadingManager.closeLoading();
    };
    
    return {
        showPageLoading,
        updateLoadingText,
        closePageLoading
    };
};