vue3页面html导出word文档

一、第三方包下载

使用npm下载以下插件:

npm install jszip-utils docxtemplater pizzip file-saver docxtemplater-image-module-free

二、总页面组件代码

<template>

  <summaryDetails

    :securityId="securityId"

    :symbol="symbol"

    id="summaryDetails"

  />

</template>

<script setup lang="ts">

import summaryDetails from './summaryDetails.vue'

const props = defineProps({

  symbol: {

    // 证券代码

    type: String,

    default: '000001'

  },

  securityId: {

    // 证券id

    type: [String, Number],

    required: true

  }

})

</script>

<style scoped lang="less"></style>

三、summaryDetails组件代码

<template>

  <ItemCard title="综述" v-loading="loading">

    <template #rightPart>

      <el-date-picker

        v-model="dateRange"

        value-format="YYYY"

        type="yearrange"

        range-separator="至"

        start-placeholder="开始年份"

        end-placeholder="结束年份"

        :editable="false"

        :disabled-date="disabledDate"

        @change="changeDate"

        @clear="changeDate1"

        class="date-select"

        ref="datePicker"

      />

      <div class="upload-box" @click="exportDataList">

        <span class="icon iconfont icon-baogaodaochu"></span>

        <span class="upload-text">报告导出</span>

      </div>

    </template>

    <template #otherContent>

      <div class="item">

        <div class="title">简述</div>

        <div class="table-box">

          <AISketch :sketch-list="sketchList"></AISketch>

        </div>

      </div>

      <div class="item">

        <div class="title">综述详情</div>

        <div class="table-box">

          <div

            class="item-detail"

            v-for="(item, index) in summaryDetailsList"

            :key="index"

          >

            <div class="detail-title">

              {{ item.title }}

              <div class="line"></div>

            </div>

            <div class="all-detail" v-html="item.overview"></div>

            <div

              class="children-item"

              v-for="(item1, index1) in item.items"

              :key="index1"

            >

              <div class="children-title">

                <div class="dot"></div>

                {{ item1.title }}

              </div>

              <div class="children-detail" v-html="item1.desc"></div>

            </div>

          </div>

        </div>

      </div>

    </template>

  </ItemCard>

</template>

<script setup lang="ts">

import JSZipUtils from 'jszip-utils'

import docxtemplater from 'docxtemplater'

import PizZip from 'pizzip'

import saveAs from 'file-saver'

import ImageModule from 'docxtemplater-image-module-free'

import { ElMessage } from 'element-plus'

import ItemCard from './../RiskWarn/ItemCard.vue'

import AISketch from '@/pages/monitor/views/stockMonitor/components/AISummary/AISketch.vue'

import { queryAISummaryData } from '@/pages/monitor/monitorApi/stockMonitor'

const props = defineProps({

  symbol: {

    // 证券代码

    type: String,

    default: ''

  },

  securityId: {

    // 证券id

    type: [String, Number],

    required: true

  },

  sketchList: {

    type: Array as any,

    default: () => {}

  }

})

interface ImageOptions {

  centered: boolean

  getImage: (chartId: string) => ArrayBuffer | false

  getSize: () => [number, number]

}

const loading = ref(false) // 加载状态

const dateRange = ref<any>([]) // 年份范围

const securityId1 = ref(props.securityId) // 证券id

const symbol1 = ref(props.symbol) // 证券代码

const sketchList = ref<any>(props.sketchList) // 简述数据

const summaryDetailsList = ref<any>([]) // 综述详情数据

const cleanedData = ref<any>([]) // 综述详情数据

const datePicker = ref() // 时间筛选框ref

// 监听参数重新赋值

watch(

  () => [props.securityId, props.symbol, props.sketchList],

  (newValue: any, oldValue: any) => {

    if (newValue[0] != oldValue[0]) {

      securityId1.value = newValue[0]

      getAISummaryData() //查询接口

    }

    if (newValue[1] != oldValue[1]) {

      symbol1.value = newValue[1]

    }

    if (newValue[2] != oldValue[2]) {

      sketchList.value = newValue[2]

    }

    dateRange.value = []

  },

  { deep: true }

)

// 控制当前时间之后不能选

const disabledDate = (time: any) => {

  const year = time.getFullYear()

  return year < 2019 || year > new Date().getFullYear()

}

// 改变时间

const changeDate = (value: any) => {

  if (value) {

    dateRange.value = toRaw(value)

    getAISummaryData()

    datePicker.value.blur()

  }

}

// 清空时间

const changeDate1 = () => {

  dateRange.value = []

  getAISummaryData()

}

const base64DataURLToArrayBuffer = (dataURL: string) => {

  const base64Regex = /^data:image\/(png|jpg|svg|svg\+xml);base64,/

  if (!base64Regex.test(dataURL)) {

    return false

  }

  const stringBase64 = dataURL.replace(base64Regex, '')

  let binaryString

  if (typeof window !== 'undefined') {

    binaryString = window.atob(stringBase64)

  } else {

    binaryString = new Buffer(stringBase64, 'base64').toString('binary')

  }

  const len = binaryString.length

  const bytes = new Uint8Array(len)

  for (let i = 0; i < len; i++) {

    const ascii = binaryString.charCodeAt(i)

    bytes[i] = ascii

  }

  return bytes.buffer

}

const downLoadDoc = (demoUrl: string, docxData: any, fileName: string) => {

  // 读取并获得模板文件的二进制内容

  JSZipUtils.getBinaryContent(demoUrl, function (error: any, content: any) {

    // 抛出异常

    if (error) {

      throw error

    }

    let opts: ImageOptions = {

      centered: false,

      getImage: (chartId: string) => {

        return base64DataURLToArrayBuffer(chartId) || false

      },

      getSize: () => {

        return [230, 120]

      }

    }

    // 创建一个PizZip实例,内容为模板的内容

    let zip = new PizZip(content)

    // 创建并加载docxtemplater实例对象

    let doc = new docxtemplater()

      .loadZip(zip)

      .attachModule(new ImageModule(opts))

    // 去除未定义值所显示的undefined

    doc.setOptions({

      nullGetter: function () {

        return ''

      }

    })

    // 设置模板变量的值,对象的键需要和模板上的变量名一致,值就是你要放在模板上的值

    doc.setData({

      ...docxData

    })

    // eslint-disable-next-line no-useless-catch

    try {

      // 用模板变量的值替换所有模板变量

      doc.render()

    } catch (error) {

      // 抛出异常

      throw error

    }

    // 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)

    let out = doc.getZip().generate({

      type: 'blob',

      mimeType:

        'application/vnd.openxmlformats-officedocument.wordprocessingml.document'

    })

    // 将目标文件对象保存为目标类型的文件,并命名

    saveAs(out, fileName)

  })

}

// 导出

const exportDataList = async () => {

  loading.value = true

  let fileName = `${

    dateRange.value && dateRange.value.length > 0

      ? `${dateRange.value[0]}-${dateRange.value[1]}综述`

      : '今年以来综述'

  }`

  // 去掉 <span style="color: #ff0000"></span> 标签

  const cleanedItems = sketchList.value.map((item: any) => ({

    ...item,

    desc: item.desc.replace(/<span style="color: #ff0000">(.*?)<\/span>/g, '$1')

  }))

  // 去掉 <br/> 标签

  cleanedData.value = summaryDetailsList.value.map((item: any) => {

    // 解决更改对象导致原数组换行标签消失问题(重新复制一份对象进行删除标签)

    const newItem = JSON.parse(JSON.stringify(item))

    return removeBrTags(newItem)

  })

  // 下载word文档

  downLoadDoc(

    'public/ai-report.docx',

    { items: cleanedItems, infos: cleanedData.value },

    fileName

  )

  loading.value = false

}

// 去掉<br/>标签方法

const removeBrTags = (obj: any) => {

  if (typeof obj === 'string') {

    return obj.replace(/<br\/>/g, '') // 去掉 <br/> 标签

  } else if (typeof obj === 'object' && obj !== null) {

    for (const key in obj) {

      if (Object.prototype.hasOwnProperty.call(obj, key)) {

        obj[key] = removeBrTags(obj[key]) // 递归处理

      }

    }

  }

  return obj

}

// 查询数据

const getAISummaryData = () => {

  loading.value = true

  sketchList.value = []

  summaryDetailsList.value = []

  queryAISummaryData(

    dateRange.value[1],

    securityId1.value,

    dateRange.value[0],

    symbol1.value

  ).then((res: any) => {

    if (res.code === 200) {

      sketchList.value = res.data.items

      summaryDetailsList.value = res.data.data

    } else {

      ElMessage({

        type: 'error',

        message: res.message

      })

    }

    loading.value = false

  })

}

onMounted(() => {

  getAISummaryData()

})

</script>

<style scoped lang="less">

.upload-box {

  margin-left: 20px;

  display: flex;

  align-items: center;

  cursor: pointer;

  font-weight: normal;

  .icon-baogaodaochu {

    font-size: 14px;

    color: #3a5bb7;

  }

  .upload-text {

    font-size: 12px;

    color: #3a5bb7;

    margin-left: 3px;

  }

}

.item {

  border: 1px solid #ebeef8;

  border-top: none;

  .title {

    display: flex;

    align-items: center;

    padding-left: 26px;

    height: 38px;

    background: #f4f7fc;

    border: 1px solid #ebeef8;

    border-left: none;

    border-right: none;

    font-weight: bold;

    font-size: 14px;

    color: #333333;

    position: relative;

    &::before {

      content: '';

      position: absolute;

      left: 15px;

      top: 50%;

      margin-top: -5px;

      width: 4px;

      height: 10px;

      background: #3a5bb7;

      border-radius: 0px 2px 2px 0px;

    }

  }

  .table-box {

    padding: 15px;

    .item-detail {

      margin-bottom: 20px;

      &:last-child {

        margin-bottom: 0;

      }

      .detail-title {

        width: 80px;

        height: 30px;

        background: rgba(58, 91, 183, 0.06);

        border-radius: 6px;

        font-size: 13px;

        color: #3a5bb7;

        display: flex;

        align-items: center;

        justify-content: center;

        margin-right: 12px;

      }

      .all-detail {

        margin: 10px 0;

      }

      .children-item {

        margin-bottom: 10px;

        font-size: 14px;

        color: #333333;

        &:last-child {

          margin-bottom: 0;

        }

        .children-title {

          margin: 5px 0;

          display: flex;

          align-items: center;

          .dot {

            width: 4px;

            height: 4px;

            background: #333333;

            margin-right: 10px;

          }

        }

      }

    }

  }

}

</style>

四、ItemCard组件代码

<template>

  <div class="item-box">

    <div class="pub-item-title">

      {{ title }}

      <div class="right-part">

        <slot name="rightPart"></slot>

      </div>

    </div>

    <div class="content-box" id="content-to-export">

      <div

        class="overview-box"

        v-if="

          title !== '综述'

        "

      >

        <div class="title">{{ title }}</div>

        <div class="content" v-html="aiText"></div>

      </div>

      <slot name="otherContent"></slot>

    </div>

  </div>

</template>

<script setup lang="ts">

const props = defineProps({

  title: {

    type: String,

    default: ''

  },

  aiText: {

    type: String,

    default: ''

  },

  sketchList: {

    type: Array as any,

    default: () => {}

  }

})

</script>

<style scoped lang="less">

.item-box {

  width: 1410px;

  margin: 0 auto;

  padding: 0 20px 20px;

  background: #ffffff;

  border-radius: 6px;

  .pub-item-title {

    :deep(.right-part) {

      display: flex;

      align-items: center;

      margin-left: auto;

      .date-select {

        width: 300px;

        height: 28px;

        --el-text-color-regular: #333333;

      }

      .drop-select {

        margin-left: 14px;

        width: 100px;

        height: 28px;

        font-weight: normal;

        --el-text-color-regular: #333333;

        .el-input__wrapper {

          padding: 0 11px;

          .el-input__inner {

            line-height: 28px;

            height: 28px;

          }

        }

        .el-select__wrapper {

          min-height: 28px;

        }

      }

    }

  }

  .content-box {

    margin-top: 14px;

    // border: 1px solid #ebeef8;

    .overview-box {

      padding: 17px 15px;

      border: 1px solid #ebeef8;

      border-bottom: none;

      .title {

        padding-left: 10px;

        line-height: 14px;

        font-size: 14px;

        color: #3a5bb7;

        position: relative;

        &::before {

          content: '';

          position: absolute;

          left: 0;

          top: 50%;

          margin-top: -2px;

          width: 4px;

          height: 4px;

          background: #3a5bb7;

        }

      }

      .content {

        margin-top: 10px;

        text-align: justify;

        color: #333333;

      }

    }

  }

}

</style>

五、AISketch组件代码

<template>

  <div class="AISketch">

    <div class="item-sketch" v-for="(item, index) in sketchList" :key="index">

      <div class="tags">{{ item.title }}</div>

      <div class="tags-info" v-html="item.desc"></div>

    </div>

  </div>

</template>

<script setup lang="ts">

const props = defineProps({

  // 简述列表

  sketchList: {

    type: Array as any,

    default: () => {}

  }

})

</script>

<style lang="less" scoped>

.AISketch {

  .item-sketch {

    display: flex;

    align-items: center;

    margin-bottom: 10px;

    &:last-child {

      margin-bottom: 0;

    }

    .tags {

      width: 80px;

      height: 30px;

      background: rgba(58, 91, 183, 0.06);

      border-radius: 6px;

      font-size: 13px;

      color: #3a5bb7;

      display: flex;

      align-items: center;

      justify-content: center;

      margin-right: 12px;

    }

    .tags-info {

      font-size: 13px;

      color: #666666;

    }

  }

}

</style>

六、在public文件夹中创建word文件(ai-report.docx)

如下编写模板(样式自调):

综述

|简述

{#items}

{title}

{desc}

{/items}

|综述详情

{#infos}

{title}

 {overview}

{#items}

·{title}

{desc}

{/items}

{/infos}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/71547.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于 STC89C52 的 8x8 点阵显示数字

一、引言 在电子设计领域,信息的有效展示是众多项目的关键环节。8x8 点阵作为一种经济且实用的显示模块,能够呈现数字、简单字母及图形等信息,在电子时钟、简易游戏机等产品中广泛应用。STC89C52 单片机凭借其低成本、丰富的 I/O 资源与稳定的性能,成为驱动 8x8 点阵的理想…

MWC 2025|紫光展锐联手美格智能发布5G通信模组SRM812

在2025年世界移动通信大会&#xff08;MWC 2025&#xff09;期间&#xff0c;紫光展锐携手美格智能正式推出了基于紫光展锐V620平台的第二代5G Sub6G R16模组SRM812&#xff0c;以超高性价比方案&#xff0c;全面赋能合作伙伴&#xff0c;加速5G规模化应用在各垂直领域的全面落…

leetcode700-二叉搜索树中的搜索

leetcode 700 思路 我们需要先了解一下二叉搜索树的特性&#xff1a; 左子树的所有节点值 < 当前节点的值。右子树的所有节点值 > 当前节点的值。这个特性适用于树中的每个节点 那么根据这个特性&#xff0c;我们可以通过根节点的值和目标值的大小来判断后序的走向&…

算法之二维装水问题

目录 1. 题目2. 解释3. 思路4. 代码5. 总结 1. 题目 给定一个数组arr&#xff0c;已知其中所有的值都是非负的&#xff0c;将这个数组看作一个容器&#xff0c;请返回容器能装多少水 比如&#xff0c;arr {3&#xff0c;1&#xff0c;2&#xff0c;5&#xff0c;2&#xff0c…

SAP FI财务凭证冲销

冲销调用FUNCTION FI_REFERENCE_CREATE实现 主要参数&#xff1a;冲销凭证&#xff0c;会计年度&#xff0c;公司代码。冲销原因&#xff0c;本期冲销还是上期冲销期间的选择。 FUNCTION ZFIFM_XXXX. *"---------------------------------------------------------------…

【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-附录D-JavaScript 工具

附录D、JavaScript 工具 JavaScript 工具 编写 JavaScript 代码与编写其他编程语言代码类似&#xff0c;都有专门的工具帮助提高开发效率。JavaScript开发者可以使用的工具一直在增加&#xff0c;这些工具可以帮助开发者更容易定位问题、优化代码和部署上线。其中有些工具是在 …

【AI学习从零至壹】Pytorch逻辑回归

Pytorch逻辑回归 线性回归简单线性回归的参数估计概率和似然的区别 最⼤似然估计似然函数对数似然函数 逻辑回归梯度下降法下⼭问题梯度与学习率学习率 梯度下降法的模拟与可视化学习率对梯度的影响学习率的最佳取值 梯度更新逻辑回归模型构建及训练流程 线性回归 线性回归的⽬…

mybatisplus 开发流程

目录 什么是mybatisplus&#xff1f; 创建项目 先创建一个简单的Java项目​编辑 引入依赖 1.引入父依赖 2.引入其他依赖 springboot配置 application.yml qppication-dev.yml 创建包 实体类 映射&#xff08;创建一个接口&#xff09; 构建测试环境 进行方法的实…

使用 uniapp 开发标准体重计算小程序

引言 在现代生活中&#xff0c;健康管理越来越受到重视&#xff0c;而体重是衡量健康状况的重要指标之一。为了方便用户快速计算自己的标准体重并了解体重状态&#xff0c;我使用 uniapp 开发了一款简单的标准体重计算小程序。本文将详细介绍开发过程&#xff0c;并分享核心代…

如何使用SSH命令安全连接并转发端口到远程服务器

ssh -p 22546 rootconnect.westc.gpuhub.com d6IS/mQKq/iG ssh -CNgv -L 6006:127.0.0.1:6006 rootconnect.westc.gpuhub.com -p 22546 第一条命令&#xff1a;用于登录远程服务器&#xff0c;进行交互式操作。第二条命令&#xff1a;用于建立 SSH 隧道&#xff0c;进行端口转…

File文件和目录

一、文件和目录相关概念 计算机文件&#xff08;File&#xff09;:以计算机硬盘为载体存储在计算机上的信息集合,可以是文本&#xff08;.txt&#xff09;、图片(.jpg、.png、.jpeg)、视频(.mp4)、程序(.exe)等&#xff0c;文件一般有拓展名&#xff0c;表示文件的类型。 文件…

Linux部署java项目

前言 Xshell下载地址 点击连接 常见命令 ls ls:显示当前目录下的文件 ll:可以显示隐藏文件和非隐藏文件与ls -l一样 ls -a -l这两个掌握就可以了 ls --help就可以知道这个后面可以跟什么 ls -al还可以这样 cd cd&#xff1a;进入文件夹 cd后面可以跟相对路径&#xff0…

如何使用 Python+Flask+win32print 实现简易网络打印服务1

Python 实现网络打印机&#xff1a;Flask win32print 在工作场景中&#xff0c;我们可能需要一个简单的网页接口&#xff0c;供他人上传文档并自动打印到指定打印机。 本文将演示如何使用 Python Flask win32print 库来实现这一需求。 代码详见&#xff1a;https://github.…

Java接口(3)与图书管理系统

抽象类与接口的区别 1.抽象类包含普通类和抽象方法&#xff0c;子类可以直接调用普通类方法不用重写。接口包含抽象方法和全局变量。 2.抽象类有各种权限&#xff0c;接口只有pubilc。 3.子类使用抽象类用extend&#xff0c;使用接口用implement。 4.一个抽象类可以实现若干…

基于Matlab的多目标粒子群优化

在复杂系统的设计、决策与优化问题中&#xff0c;常常需要同时兼顾多个相互冲突的目标&#xff0c;多目标粒子群优化&#xff08;MOPSO&#xff09;算法应运而生&#xff0c;作为群体智能优化算法家族中的重要成员&#xff0c;它为解决此类棘手难题提供了高效且富有创新性的解决…

Python 爬取唐诗宋词三百首

你可以使用 requests 和 BeautifulSoup 来爬取《唐诗三百首》和《宋词三百首》的数据。以下是一个基本的 Python 爬虫示例&#xff0c;它从 中华诗词网 或类似的网站获取数据并保存为 JSON 文件。 import requests from bs4 import BeautifulSoup import json import time# 爬取…

美股回测:历史高频分钟数据的分享下载与策略解析20250305

美股回测&#xff1a;历史高频分钟数据的分享下载与策略解析20250305 在金融分析和投资决策的精细化过程中&#xff0c;美股历史分钟高频数据发挥着至关重要的作用。这些数据以其详尽性和精确性&#xff0c;记录了股票每分钟的价格波动和成交量变化&#xff0c;为投资者提供了…

辛格迪客户案例 | 深圳善康医药科技GMP培训管理(TMS)项目

01 善康医药&#xff1a;创新药领域的探索者 深圳善康医药科技股份有限公司自2017年创立以来&#xff0c;便扎根于创新药研发领域&#xff0c;专注于成瘾治疗药物的研究、生产与销售。公司坐落于深圳&#xff0c;凭借自身独特的技术优势与研发实力&#xff0c;在行业内逐渐崭露…

【长安大学】苹果手机/平板自动连接认证CHD-WIFI脚本(快捷指令)

背景&#xff1a; 已经用这个脚本的记得设置Wifi时候&#xff0c;关闭“自动登录” 前几天实在忍受不了CHD-WIFI动不动就断开&#xff0c;一天要重新连接&#xff0c;点登陆好几次。试了下在网上搜有没有CHD-WIFI的自动连接WIFI自动认证脚本&#xff0c;那样我就可以解放双手&…

Vue+el-upload配置minIO实现大文件的切片并发上传、上传进度展示、失败重试功能

vue3el-upload实现切片上传 效果图 初始界面 上传中的界面 上传完成的界面 上传失败的界面 <template><div><el-uploadclass"BigFileUpload"ref"uploadRef"action"#"drag:show-file-list"false":on-change"…