h5封装table表格 vue

文章介绍了一位开发者如何在Vue2中封装一个可维护的表格组件,包括设置滚动、边框、圆角、文字对齐等样式,并提供了源码。组件支持动态配置列标题、数据源,还进行了日期格式化处理,同时提供了空数据时的占位显示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

vue2

table表格

因某些原因,开发时总是需要在h5页面上开发表格, 所以就自己封装了一个, 这样就便于维护.一下直接提供源码, 不足之处, 请不吝赐教.

先来看看效果图:
在这里插入图片描述
在这里插入图片描述

以下为源码:
<template>
  <div
      class="cuTable"
      :style="{
        ...scroll && {
          overflowX: 'scroll'
        },
        ...!scroll && {
          overflow: 'hidden'
        }
      }"
  >
    <div :class="border ? 't-table_border' : ''">
      <table :class="round ? 't-table_th_round' : ''">
        <tr v-if="tableTitle">
          <th :colspan="params.length"
              :style="{
                background: hBgColor,
                borderBottom: 'none',
                color: '#232A33',
                fontSize: '14px'
              }"
          >
            {{ tableTitle }}
          </th>
        </tr>
        <tr>
          <th v-for="(item, index) in params"
              :style="{
                width: item.width ? item.width : 'auto',
                height: rowHeight,
                background: hBgColor,
                color: hColor,
                textAlign: align,
                whiteSpace: scroll ? 'nowrap' : 'wrap'
              }"
              :key="index+'p'"
          >
            <span :style="{whiteSpace: item.whiteSpace ? 'wrap' : 'nowrap'}">{{ item.title }}</span>
          </th>
        </tr>
        <tr
            v-show="list && list.length > 0"
            v-for="(item, index) in list"
            :key="index+'p1'"
            @click="rowClick(item)"
            :style="{
              background: rowBgColor && (index%2 !== 0 ? rowBgColor : '#fff'),
            }"
        >
          <td
              v-for="(ite, idx) in params"
              :key="idx+'p2'"
              :style="{
                textAlign: align,
                wordBreak: scroll ? '' : 'break-word',
              }"
          >
            <span v-if="wordWrap && wordWrap === ite.labelKey" style="white-space: nowrap">{{
                item[ite.labelKey]
              }}</span>
            <span v-else>{{ item[ite.labelKey] }}</span>
          </td>
        </tr>
        <tr v-if="list.length === 0">
          <td :colspan="params.length">
            <slot name="empty">
              <div style="color: #666; font-size: 14px; padding: 6px; text-align: center">暂无数据</div>
            </slot>
          </td>
        </tr>
      </table>
    </div>

  </div>
</template>

<script>
export default {
  name: "cuTable",
  data() {
    return {
      list: []
    }
  },
  props: {
    params: { // 配置参数 title: 标题, labelKey: 数据取值的key  width: 表格宽度, whiteSpace: 标题是否换行
      type: Array,
      default: () => {
        return ([])
      }
    },
    dataSource: { // 数据
      type: Array,
      default: () => {
        return ([])
      }
    },
    tableTitle: { // 表格标题
      type: String
    },
    wordWrap: { // 表格项数据不换行, key
      type: String
    },
    border: { // 边框
      type: Boolean,
      default: true
    },
    scroll: { // 左右滑动
      type: Boolean,
      default: true
    },
    round: { // 边框圆角
      type: Boolean,
      default: true
    },
    borderColor: { // 边框颜色
      type: String,
      default: '#D4E6FF'
    },
    hBgColor: { // 头背景颜色
      type: String,
      default: '#EDF5FF'
    },
    hColor: { // 头字体颜色
      type: String,
      default: '#868C94'
    },
    align: { // 文字对齐方式
      type: String,
      default: 'left'
    },
    rowHeight: { // 行高
      type: String,
      default: '31px'
    },
    rowBgColor: { // 行背景色 n+1行
      type: [String, Boolean],
      default: '#fff'
    },
    format: { // 格式
      type: String,
      default: 'YYYY.MM.DD'
    },
    formatKey: {
      type: String,
      default: "PREM_DUE_DATE"
    },
  },
  watch: {
    dataSource: {
      deep: true,
      handler(newV, oldV) {
        if (newV !== oldV) {
          this.initData()
        }
      }
    }
  },
  mounted() {
    this.initData()
  },
  methods: {
    initData() {
      this.list = JSON.parse(JSON.stringify(this.dataSource))
      if (this.format && this.formatKey) {
        this.list.map(item => {
          if (item[this.formatKey] && item[this.formatKey].indexOf('9999') > -1) {
            item[this.formatKey] = '终身'
          } else {
            let date = new Date(item[this.formatKey]);
            if (date) {
              let y = date.getFullYear()
              let m = date.getMonth() + 1 > 9 ? date.getMonth() + 1 : `0${date.getMonth() + 1}`
              let d = date.getDate() + 1 > 9 ? date.getDate() + 1 : `0${date.getDate() + 1}`
              let h = date.getHours() + 1 > 9 ? date.getHours() + 1 : `0${date.getHours() + 1}`
              let ms = date.getMinutes() + 1 > 9 ? date.getMinutes() + 1 : `0${date.getMinutes() + 1}`
              let s = date.getSeconds() + 1 > 9 ? date.getSeconds() + 1 : `0${date.getSeconds() + 1}`
              if (this.format === 'YYYY.MM.DD') {
                item[this.formatKey] = `${y}.${m}.${d}`
              }
              if (this.format === 'YYYY.MM.DD hh:mm:ss') {
                item[this.formatKey] = `${y}.${m}.${d} ${h}:${ms}:${s}`
              }
              if (this.format === 'YYYY-MM-DD') {
                item[this.formatKey] = `${y}-${m}-${d}`
              }
              if (this.format === 'YYYY-MM-DD hh:mm:ss') {
                item[this.formatKey] = `${y}-${m}-${d} ${h}:${ms}:${s}`
              }
            }
          }
        })
      }
    },

    rowClick(row) {
      this.$emit('onClick', row)
    },
  }
}
</script>

<style lang="less" scoped>
table {
  width: 100%;
  margin: auto;
  border-spacing: 0;
  font-size: 14px;
}

.t-table_border {
  table {
    width: 100%;
    margin: auto;
    border-spacing: 0;
    font-size: 14px;
  }

  table tr > th {
    text-align: center;
    border: 1px solid #D4E6FF;
    padding: 4px;
  }

  table tr > th:nth-child(2n) {
    border-right: none;
    border-left: none;
  }

  table tr > th:last-child {
    border-right: 1px solid #D4E6FF;
  }

  table tr > td {
    text-align: center;
    border: 1px solid #D4E6FF;
    border-top: none;
    padding: 4px;
  }

  table tr > td:nth-child(2n) {
    border-right: none;
    border-left: none;
  }

  table tr > td:last-child {
    border-right: 1px solid #D4E6FF;
  }
}

.t-table_th_round {
  tr:first-child th:first-child {
    border-top-left-radius: 8px;
  }

  tr:first-child th:last-child {
    border-top-right-radius: 8px;
  }

  tr:last-child {
    td:first-child {
      border-bottom-left-radius: 8px;
    }
  }

  tr:last-child {
    td:last-child {
      border-bottom-right-radius: 8px;
    }
  }
}
</style>
往下是使用例子:
<cuTable
                  :tableTitle="`${ item.clientName }的个人${ type }`"
                  :params="params"
                  :data-source="dataSource"
                  wordWrap="enddate"
              />

data() {
	return {
	dataSource: [{liaBesc: '保险'}, {liaBexp: '保额(万元)'}, endDate: "2022-02-01"}],
	params: [{
        title: '保险责任',
        labelKey: 'liaBesc'
      }, {
        title: '保额(万元)',
        labelKey: 'liaBexp',
        whiteSpace: false
      }, {
        title: '保险期间',
        labelKey: 'endDate'
      }]

}
<think>好的,我现在需要帮助用户解决在Vue3和Vant框架下实现移动端H5中PDF和DOCX文件在线预览的问题。首先,我得理清用户的需求。用户提到了之前的上传和下载问题,现在需要预览功能,特别是PDF和DOCX格式的文件。移动端H5环境,所以可能需要考虑不同浏览器的兼容性,特别是微信内置浏览器或者其他常见移动浏览器。 首先,我需要回忆常见的文件预览方案。对于PDF,通常可以使用pdf.js这样的库,它可以在浏览器中渲染PDF文件。而DOCX文件预览比较麻烦,因为浏览器不能直接渲染,可能需要转换成PDF或者其他格式,或者使用第三方服务如Office Online Viewer。 接下来,结合Vue3和Vant,可能需要考虑如何集成这些库到项目中。Vant主要是UI框架,所以样式和组件使用应该没问题,但预览功能主要依赖于其他库或服务。 对于PDF预览,pdf.js是一个不错的选择,但移动端的适配可能需要调整,比如响应式布局,触控操作的支持。另外,移动端可能需要处理文件流的获取,比如通过后端接口返回文件数据,前端使用pdf.js渲染。 对于DOCX文件,用户提到在线预览,可能需要将DOCX转换为PDF再预览,或者使用微软的在线预览服务,比如在iframe中嵌入Office Online Viewer的URL。不过需要注意,Office Online Viewer已经停止服务,可能需要寻找替代方案,比如使用Google Docs的预览功能,或者使用后端转换服务,将DOCX转成HTML或PDF,再由前端展示。 另外,用户之前提到了文件上传和下载的问题,可能需要确保文件在预览前已经正确上传,并且有正确的URL或文件流提供给预览组件。 还需要考虑移动端的性能问题,PDF.js在移动端可能会比较吃性能,特别是大文件,可能需要分页加载或者优化渲染方式。DOCX转换可能需要后端支持,比如调用LibreOffice或者其他的转换工具,将DOCX转为PDF,然后前端使用PDF预览的方式处理。 另外,用户可能没有明确是否需要编辑功能,但根据问题描述,只需要预览,所以主要关注展示即可。 然后,我需要考虑具体的实现步骤: 1. PDF预览:使用pdf.js库,在Vue3中封装成组件,接收文件URL或Blob,渲染成Canvas或SVG。 2. DOCX预览:可能需要使用docx-preview这样的库,或者后端转换服务。但docx-preview可能在移动端的兼容性有问题,或者需要调整样式。或者使用第三方在线服务,但需注意隐私和网络问题。 3. 移动端适配:Vant的弹窗或页面布局,全屏展示,手势支持缩放等。 4. 文件获取方式:可能需要先下载文件到本地,或者直接通过URL在线预览。如果文件在服务器上,需要处理跨域问题,或者通过后端代理获取文件。 然后,检查是否有现成的Vue3组件或示例代码可以参考。例如,pdf.jsVue封装组件,可能需要查找Vue3兼容的版本。DOCX预览可能需要自己实现,或者使用mammoth.js将DOCX转成HTML,然后在页面中显示。 另外,考虑到移动端H5,在微信浏览器中可能需要处理权限问题,比如是否允许直接打开文件链接。可能需要使用微信JS-SDK的接口,或者在浏览器中直接打开预览页面。 还需要注意,如果文件是敏感数据,直接通过URL访问可能有安全风险,所以可能需要通过后端进行鉴权,或者生成临时访问链接。 总结可能的方案: - PDF:使用pdf.js,在Vue3中集成,实现分页、缩放等功能。 - DOCX:使用mammoth.js转成HTML,或者使用后端转换服务转成PDF,再使用pdf.js预览。 或者,对于DOCX,使用Google Docs的预览功能,通过iframe嵌入类似`https://docs.google.com/gview?url=文件URL&embedded=true`,但需要确认该服务是否可用,以及网络访问是否稳定。 另外,可能需要处理不同文件的MIME类型,确保浏览器能正确识别。 现在需要把这些思路整理成一个清晰的步骤,并提供代码示例,比如如何使用pdf.jsVue3中创建预览组件,如何处理DOCX的转换等。同时,需要注意移动端的优化,比如触控手势、加载状态提示、错误处理等。 可能遇到的问题包括: 1. 移动端浏览器对PDF内嵌支持不足,需要pdf.js来增强。 2. DOCX转HTML的样式问题,可能需要自定义CSS来适配移动端。 3. 大文件的加载性能,需要分页加载或懒加载。 4. 后端服务的依赖,如果使用转换服务,需要确保后端可用。 最后,需要验证这些方案的可行性,比如测试pdf.js在移动端的表现,测试mammoth.js转换后的效果,或者确认第三方服务的可用性。</think>### Vue3 + Vant 移动端H5文件预览实现方案 #### 一、PDF文件预览方案 1. **使用pdf.js实现本地渲染** - 安装依赖:`yarn add pdfjs-dist` - 创建PDF预览组件: ```vue <template> <van-popup v-model:show="showPreview" position="bottom"> <div class="pdf-container"> <canvas v-for="page in pages" :key="page" ref="canvasRef"></canvas> </div> </van-popup> </template> <script setup> import { ref } from 'vue'; import * as pdfjsLib from 'pdfjs-dist/build/pdf'; import 'pdfjs-dist/build/pdf.worker.entry'; const props = defineProps({ fileUrl: String }); const showPreview = ref(false); const pages = ref([]); const canvasRef = ref([]); const renderPDF = async () => { const loadingTask = pdfjsLib.getDocument(props.fileUrl); const pdf = await loadingTask.promise; pages.value = Array.from({ length: pdf.numPages }, (_, i) => i + 1); pages.value.forEach(async (pageNumber) => { const page = await pdf.getPage(pageNumber); const viewport = page.getViewport({ scale: 1.5 }); const canvas = canvasRef.value[pageNumber - 1]; const context = canvas.getContext('2d'); canvas.height = viewport.height; canvas.width = viewport.width; await page.render({ canvasContext: context, viewport: viewport }).promise; }); }; const openPreview = () => { showPreview.value = true; nextTick(() => renderPDF()); }; </script> ``` 2. **移动端优化技巧** - 使用Vant的`van-popup`组件实现底部弹窗 - 添加手势缩放(需集成`@use-gesture/vanilla`库) - 支持分页加载提升性能 #### 二、DOCX文件预览方案 1. **方案对比** | 方案 | 优点 | 缺点 | |---------------------|-----------------------|-----------------------| | 后端转换PDF | 显示效果统一 | 增加服务器负载 | | mammoth.js转HTML | 纯前端实现 | 复杂格式可能丢失 | | 第三方服务嵌入 | 无需开发 | 依赖外部网络 | 2. **推荐实现:mammoth.js转HTML** ```vue <template> <van-popup v-model:show="showDocxPreview"> <div class="docx-preview" v-html="docxContent"></div> </van-popup> </template> <script setup> import { ref } from 'vue'; import mammoth from 'mammoth'; const docxContent = ref(''); const previewDocx = async (file) => { const arrayBuffer = await file.arrayBuffer(); const result = await mammoth.convertToHtml({ arrayBuffer }); docxContent.value = result.value; }; </script> <style> .docx-preview { padding: 12px; max-width: 100%; img { max-width: 100% } table { border-collapse: collapse } } </style> ``` #### 三、通用文件处理逻辑 1. **文件获取方式** ```javascript // 从服务器获取文件 const fetchFile = async (url) => { const response = await fetch(url); return await response.blob(); }; // 本地文件选择 const handleFileUpload = (e) => { const file = e.target.files[0]; if (file.type === 'application/pdf') { previewPDF(URL.createObjectURL(file)); } else if (file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') { previewDocx(file); } }; ``` 2. **移动端适配要点** - 使用`@vant/touch-emulator`处理手势事件 - 添加加载状态提示 - 异常处理: ```javascript try { await renderPDF(); } catch (error) { showNotify({ type: 'danger', message: '文件加载失败' }); } ``` #### 四、备选方案:使用浏览器原生能力 ```javascript // 直接打开新窗口(需确保MIME类型正确) const openNativePreview = (url) => { window.open(url, '_blank'); }; // 微信浏览器特殊处理 if (/MicroMessenger/i.test(navigator.userAgent)) { wx.miniProgram.navigateTo({ url: `/pages/preview?fileUrl=${encodeURIComponent(url)}` }); } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值