腾讯云开发者社区 JS之文件预览功能的实现

JS之文件预览功能的实现

需求:JS实现文件预览功能,可预览图片、Word文档、Excel表格、PDF文件、TXT文件。Word预览:需要安装docx-previewExcel预览:需要安装xlsxPDF预览:需要安装pdfh5登录后复制npm i pdfh5 -D1.如果报错登录后复制[vite] error while updat...

李qy  ·  2024-07-01 02:17:25 发布

需求:JS实现文件预览功能,可预览图片、Word文档、Excel表格、PDF文件、TXT文件。

 

Word预览:

需要安装docx-preview

 

Excel预览:

需要安装xlsx

 

PDF预览:

需要安装pdfh5

npm i pdfh5 -D
  • 1.

如果报错

[vite] error while updating dependencies:
Error: Build failed with 1 error:
node_modules/pdfh5/js/pdf.js:22:244110: ERROR: Could not resolve "canvas"
  • 1.
  • 2.
  • 3.

则需要安装canvas,因为pdfh5依赖canvas

npm i canvas -D
  • 1.

 

TXT预览:

txtToUtf8.js

export const txtToUtf8 = (file) => {
  return new Promise(async (resolve, reject) => {
    const codeType = await getUnicodeType(file);
    if (codeType === 'utf-8') {
      return resolve(file);
    };
    let newBlob = null
    let render = new FileReader()
    render.readAsText(file, 'gb2312')
    render.onload = (res) => {
      newBlob = new Blob([res.target.result], {
        type: "text/plain"
      })
      let newFile = new File([newBlob], file.name, {
        type: newBlob.type
      })
      resolve(newFile)
    }
    render.onerror = (err) => {
      reject(err)
    }
  })
}

// 获取txt文件编码类型
const getUnicodeType = (file) => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = function (e) {
      var v8 = new Uint8Array(e.target.result);
      if (isUTF8(v8)) {
        resolve("utf-8")
      } else {
        resolve("gbk")
      }
    };
    reader.onerror = e => {
      reject(e);
    };
    reader.readAsArrayBuffer(file);
  })
}

// 判断是否为UTF8编码类型的文件
const isUTF8 = (bytes) => {
  var i = 0;
  while (i < bytes.length) {
    if (( // ASCII
      bytes[i] == 0x09 ||
      bytes[i] == 0x0A ||
      bytes[i] == 0x0D ||
      (0x20 <= bytes[i] && bytes[i] <= 0x7E)
    )) {
      i += 1;
      continue;
    }

    if (( // non-overlong 2-byte
      (0xC2 <= bytes[i] && bytes[i] <= 0xDF) &&
      (0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xBF)
    )) {
      i += 2;
      continue;
    }

    if (( // excluding overlongs
      bytes[i] == 0xE0 &&
      (0xA0 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
      (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF)
    ) ||
      ( // straight 3-byte
        ((0xE1 <= bytes[i] && bytes[i] <= 0xEC) ||
          bytes[i] == 0xEE ||
          bytes[i] == 0xEF) &&
        (0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
        (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF)
      ) ||
      ( // excluding surrogates
        bytes[i] == 0xED &&
        (0x80 <= bytes[i + 1] && bytes[i + 1] <= 0x9F) &&
        (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF)
      )
    ) {
      i += 3;
      continue;
    }

    if (( // planes 1-3
      bytes[i] == 0xF0 &&
      (0x90 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
      (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&
      (0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)
    ) ||
      ( // planes 4-15
        (0xF1 <= bytes[i] && bytes[i] <= 0xF3) &&
        (0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xBF) &&
        (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&
        (0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)
      ) ||
      ( // plane 16
        bytes[i] == 0xF4 &&
        (0x80 <= bytes[i + 1] && bytes[i + 1] <= 0x8F) &&
        (0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xBF) &&
        (0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xBF)
      )
    ) {
      i += 4;
      continue;
    }
    return false;
  }
  return true;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.

 

预览相关的全部代码示例如下:

UploadFile.vue

<template>
  <div :class="subFormItem ? 'sub-comp' : 'custom-comp'">
    <van-field
      :label="item.label"
      :required="item.isConfigRequired ?? item.isRequired"
      placeholder="点击上传"
      readonly
    >
      <template #input>
        <div class="uploader">
          <van-row>
            <van-col
              v-for="(file, index) in formData[item.prop]"
              :key="file.objectName"
              span="24"
            >
              <div class="doc-file-item">
                <img
                  class="file-icon"
                  src="@/assets/file/png-ext.png"
                  v-if="getFileType(file.fileName) === 'png'"
                />
                <img
                  class="file-icon"
                  src="@/assets/file/jpg-ext.png"
                  v-if="
                    getFileType(file.fileName) === 'jpg' ||
                    getFileType(file.fileName) === 'jpeg'
                  "
                />
                <img
                  class="file-icon"
                  src="@/assets/file/gif-ext.png"
                  v-if="getFileType(file.fileName) === 'gif'"
                />
                <img
                  class="file-icon"
                  src="@/assets/file/ppt-ext.png"
                  v-if="
                    getFileType(file.fileName) === 'ppt' ||
                    getFileType(file.fileName) === 'pptx'
                  "
                />
                <img
                  class="file-icon"
                  src="@/assets/file/pdf-ext.png"
                  v-if="getFileType(file.fileName) === 'pdf'"
                />
                <img
                  class="file-icon"
                  src="@/assets/file/word-ext.png"
                  v-if="
                    getFileType(file.fileName) === 'docx' ||
                    getFileType(file.fileName) === 'doc'
                  "
                />
                <img
                  class="file-icon"
                  src="@/assets/file/excel-ext.png"
                  v-if="
                    getFileType(file.fileName) === 'xlsx' ||
                    getFileType(file.fileName) === 'xls'
                  "
                />
                <img
                  class="file-icon"
                  src="@/assets/file/zip-ext.png"
                  v-if="getFileType(file.fileName) === 'zip'"
                />
                <img
                  class="file-icon"
                  src="@/assets/file/txt-ext.png"
                  v-if="getFileType(file.fileName) === 'txt'"
                />
                <img
                  class="file-icon"
                  src="@/assets/file/unknown-ext.png"
                  v-if="getFileType(file.fileName) === ''"
                />
                <span @click="previewFile(file)">{{ file.fileName }}</span>
                <div
                  class="file-delete"
                  @click="deleteFile(formData, index, item)"
                  v-show="!isReadOnly"
                >
                  <van-icon name="cross" size="12" />
                </div>
              </div>
            </van-col>
            <div v-if="!formData[item.prop] && isReadOnly">无附件</div>
          </van-row>
          <van-uploader
            :accept="acceptFileTypes.join(',')"
            :multiple="true"
            :preview-image="false"
            :after-read="(file) => uploaderAfter(file, 'file')"
            v-show="!isReadOnly"
          >
            <van-button icon="plus" type="primary">点击上传</van-button>
          </van-uploader>
        </div>
      </template>
    </van-field>
  </div>
  <UploadProgress
    :loading="uploadLoading"
    :rate="uploadRate"
    :show-progress="showProgress"
  />
  <van-popup
    v-model:show="previewVisible"
    round
    closeable
    position="bottom"
    :style="{ height: '90%', padding: '16px' }"
  >
    <FilePreview :file="currentFile" />
  </van-popup>
</template>

<script setup>
import {
  onMounted,
  ref,
  toRefs,
  defineAsyncComponent,
  computed,
  watch,
} from "vue";
import { Toast, Dialog } from "vant";
import ActivitiApi from "@/api/activiti";
import { checkMobileModel } from "@/utils/androidOrIos";
import {
  hasOptionsComp,
  isUploaderComp,
  PROCESS_ORDER_STATUS,
  acceptFileTypes,
  deleteFile,
  sensitiveInfoSalt,
  reportUsageRecord,
} from "@/utils/common";
import compressorImage from "@/utils/compressor";
import dayjs from "dayjs";
import UploadProgress from "@/components/uploadProgress/uploadProgress.vue";
import { useRoute, useRouter } from "vue-router";
import { txtToUtf8 } from "@/utils/txtToUtf8";

const FilePreview = defineAsyncComponent(() =>
  import("@/components/filePreview/FilePreview.vue")
);

const props = defineProps({
  formData: Object,
  item: Object,
  isReadOnly: Object,
  compList: Array,
  reportType: String,
  wholeFormData: Object,
  subFormItem: Object,
  subFormIdx: Number,
});
const {
  formData,
  item,
  isReadOnly,
  compList,
  reportType,
  wholeFormData,
  subFormItem,
  subFormIdx,
} = toRefs(props);

watch(
  () => formData.value[item.value.prop],
  (val) => {
    if (wholeFormData.value) {
      const resArr = JSON.parse(
        wholeFormData.value[subFormItem.value.prop] ?? "[]"
      );
      if (!resArr[subFormIdx.value]) {
        resArr[subFormIdx.value] = {};
      }
      resArr[subFormIdx.value][item.value.prop] = val;
      wholeFormData.value[subFormItem.value.prop] = JSON.stringify(resArr);
      sessionStorage.formState = JSON.stringify(wholeFormData.value);
    }
  }
);

const router = useRouter();
const route = useRoute();

const previewVisible = ref(false);
const currentFile = ref();
const previewFile = (file) => {
  if (
    getFileType(file.fileName) == "ppt" ||
    getFileType(file.fileName) == "pptx"
  ) {
    Toast.fail("ppt文件暂不支持预览");
  } else {
    reportDownLoad();

    const params = {
      env: "internet",
      fileName: file.objectName,
    };

    ActivitiApi.getBatchFileUrl(params)
      .then((res) => {
        const data = res.data;
        if (data.code === 200) {
          previewVisible.value = true;
          file.fileUrl = data.data;
          currentFile.value = file;
        } else {
          throw new Error(data.message);
        }
      })
      .catch((err) => {
        if (err.message) {
          Toast.fail(err.message);
        } else {
          Toast.fail("上传失败");
        }
      });
  }
};

const reportDownLoad = () => {
  const data = {
    landingPageClass: "unknown",
    landingPageSubclass: "FileDownload",
    landingPageRelateName:
      route.query.workConfCode ?? sessionStorage.workConfCode,
    quantity: 1,
    accessSource: route.query.workConfCode ? "sso" : "frontEndRoute",
  };
  if (reportType.value) {
    data.landingPageClass = reportType.value;
  }
  if (sessionStorage.eAppInfo) {
    data.landingPageClass = "eApp";
    data.landingPageRelateName = JSON.parse(
      sessionStorage.eAppInfo ?? "{}"
    ).code;
  }
  reportUsageRecord(data);
};

const uploadLoading = ref(false);
const uploadRate = ref(0);
const showProgress = ref(false);
const uploaderAfter = async (file, type) => {
  if (!file) {
    return;
  }

  uploadLoading.value = true;
  const uploadFormData = new FormData();

  if (file instanceof Array) {
    let fileList = file;

    if (type === "image") {
      fileList = await compressorImage(fileList);

      fileList.forEach((item) => {
        uploadFormData.append("fileList", item.file);
      });
    } else {
      const txts = [];
      const tasks = [];

      file.forEach((item) => {
        if (getFileType(item.file.name) === "txt") {
          txts.push(item.file);
        } else {
          uploadFormData.append("fileList", item.file);
        }
      });

      txts.forEach((txt) => {
        tasks.push(txtToUtf8(txt));
      });

      await Promise.all(tasks)
        .then((txtList) => {
          txtList.forEach((txt) => {
            uploadFormData.append("fileList", txt);
          });
        })
        .catch((err) => {
          txts.forEach((txt) => {
            uploadFormData.append("fileList", txt);
          });
        });
    }
  }

  if (file.constructor === Object) {
    if (type === "image") {
      let fileObj = file;
      fileObj = await compressorImage(fileObj);
      uploadFormData.append("fileList", fileObj.file);
    } else {
      if (getFileType(file.file.name) === "txt") {
        await txtToUtf8(file.file)
          .then((txt) => {
            uploadFormData.append("fileList", txt);
          })
          .catch((err) => {
            uploadFormData.append("fileList", file.file);
          });
      } else {
        uploadFormData.append("fileList", file.file);
      }
    }
  }

  showProgress.value = true;
  uploadFile(uploadFormData)
    .then((res) => {
      if (formData.value[item.value.prop]) {
        formData.value[item.value.prop] =
          formData.value[item.value.prop].concat(res);
      } else {
        formData.value[item.value.prop] = res;
      }
    })
    .finally(() => {
      uploadRate.value = 0;
      uploadLoading.value = false;
      showProgress.value = false;
    });
};

const uploadFile = (data) => {
  return new Promise((resolve, reject) => {
    ActivitiApi.uploadBatchFile(data, (progress) => {
      uploadRate.value = Math.round((progress.loaded / progress.total) * 100);
    })
      .then((res) => {
        const data = res.data;
        if (data.code === 200) {
          Toast.success("上传成功");
          resolve(data.data);
        } else {
          throw new Error(data.message);
        }
      })
      .catch((err) => {
        if (err.message) {
          Toast.fail(err.message);
        } else {
          Toast.fail("上传失败");
        }
        reject(err);
      });
  });
};

const uploadImage = (data) => {
  return new Promise((resolve, reject) => {
    ActivitiApi.uploadBatchImage(data, (progress) => {
      uploadRate.value = Math.round((progress.loaded / progress.total) * 100);
    })
      .then((res) => {
        const data = res.data;
        if (data.code === 200) {
          Toast.success("上传成功");
          resolve(data.data);
        } else {
          throw new Error(data.message);
        }
      })
      .catch((err) => {
        if (err.message) {
          Toast.fail(err.message);
        } else {
          Toast.fail("上传失败");
        }
        reject(err);
      });
  });
};

const openUpload = (event, data) => {
  if (data.waterMarkConfigComps?.length) {
    const result = data.waterMarkConfigComps.find((prop) => {
      return !formData.value[prop];
    });

    const target = compList.value.find((comp) => {
      return result === comp.prop;
    });

    if (target) {
      if (target.type !== "title" && target.type !== "noticeBar") {
        Toast.fail(target.label + "不能为空");
        event.preventDefault();
      }
    }
  }
};

const onOversize = (file) => {
  Toast.fail("文件大小不能超过50MB");
};

const getFileType = (fileName) => {
  if (!fileName) {
    return "";
  }
  let flag = fileName.split(".");
  return flag[flag.length - 1];
};
</script>

<style scoped lang="less">
.custom-comp {
  margin: 0px 20px;
  width: 90%;
}
.component {
  .custom-comp {
    margin: 0;
    width: 100%;
  }
}
.uploader {
  width: 100%;
  display: flex;
  flex-direction: column;
}

.doc-file-item {
  width: 100%;
  margin-bottom: 18px;
  display: flex;
  flex-shrink: 0;
  align-items: center;
}

.doc-file-item span {
  width: 100%;
  margin: 0 8px;
  font-size: 12px;
  line-height: 18px;
  word-break: break-all;
}

.file-delete {
  width: 16px;
  height: 16px;
  display: flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: #ffffff;
  background: rgba(0, 0, 0, 0.6);
}

.file-icon {
  width: 30px;
  height: auto;
}
</style>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.
  • 363.
  • 364.
  • 365.
  • 366.
  • 367.
  • 368.
  • 369.
  • 370.
  • 371.
  • 372.
  • 373.
  • 374.
  • 375.
  • 376.
  • 377.
  • 378.
  • 379.
  • 380.
  • 381.
  • 382.
  • 383.
  • 384.
  • 385.
  • 386.
  • 387.
  • 388.
  • 389.
  • 390.
  • 391.
  • 392.
  • 393.
  • 394.
  • 395.
  • 396.
  • 397.
  • 398.
  • 399.
  • 400.
  • 401.
  • 402.
  • 403.
  • 404.
  • 405.
  • 406.
  • 407.
  • 408.
  • 409.
  • 410.
  • 411.
  • 412.
  • 413.
  • 414.
  • 415.
  • 416.
  • 417.
  • 418.
  • 419.
  • 420.
  • 421.
  • 422.
  • 423.
  • 424.
  • 425.
  • 426.
  • 427.
  • 428.
  • 429.
  • 430.
  • 431.
  • 432.
  • 433.
  • 434.
  • 435.
  • 436.
  • 437.
  • 438.
  • 439.
  • 440.
  • 441.
  • 442.
  • 443.
  • 444.
  • 445.
  • 446.
  • 447.
  • 448.
  • 449.
  • 450.
  • 451.
  • 452.
  • 453.
  • 454.
  • 455.
  • 456.
  • 457.
  • 458.
  • 459.
  • 460.
  • 461.
  • 462.
  • 463.
  • 464.
  • 465.
  • 466.
  • 467.
  • 468.
  • 469.

FilePreview.vue

<script setup>
import {
  ref,
  onMounted,
  getCurrentInstance,
  reactive,
  toRefs,
  watch,
  nextTick,
} from "vue";
import * as docx from "docx-preview";
import * as XLSX from "xlsx";
import Pdfh5 from "pdfh5";
import "pdfh5/css/pdfh5.css";
import request from "../../utils/request";
import { Toast } from "vant";

const { proxy } = getCurrentInstance();
const typeName = ref("");
const imgUrl = ref("");
const srcList = ref();
const loading = ref(false);
const pdfUrl = ref("");
const pdfh5 = ref(null);
const pptUrl = ref("");
const txtUrl = ref("");
const docFile = ref(null);
const txtText = ref("");

const emits = defineEmits();
const emptyTips = ref("暂无内容");
const fullscreen = ref(false);
const data = reactive({
  excel: {
    // 数据
    workbook: {},
    // 表名称集合
    sheetNames: [],
    // 激活项
    sheetNameActive: "",
    // 当前激活表格
    SheetActiveTable: "",
  },
});
const props = defineProps({
  showTime: {
    type: Boolean,
    default: false,
  },
  file: {
    type: Object,
    default: {},
  },
  clientHeight: {
    type: Number,
    default: 600,
  },
});

const { excel } = toRefs(data);
const wordUrl = ref("");

onMounted(() => {
  loading.value = true;
  let fileType = props.file.fileName.split(".");
  fileType = fileType[fileType.length - 1];
  init(fileType);
});

// 前一个页面调用的init  我在前一个页面根据文件名字后缀已经判断是什么类型的文件了
const init = (type) => {
  typeName.value = type;
  if (
    type.startsWith("JPG") ||
    type.startsWith("JPEG") ||
    type.startsWith("PNG") ||
    type.startsWith("jpg") ||
    type.startsWith("jpeg") ||
    type.startsWith("png")
  ) {
    request({
      method: "GET",
      url: props.file.fileUrl,
      responseType: "blob", //告诉服务器想到的响应格式
      headers: {
        Accept: "application/octet-stream",
      },
    })
      .then((res) => {
        if (res) {
          let blob = new Blob([res.data], { type: "image/jpg" });
          const imageUrl = URL.createObjectURL(blob);
          imgUrl.value = imageUrl;
          (srcList.value = [imageUrl]), (loading.value = false);
        } else {
          loading.value = false;
        }
      })
      .catch(function (error) {
        console.log(error);
        loading.value = false;
      });
  } else if (type == "pdf") {
    request({
      method: "GET",
      url: props.file.fileUrl,
      responseType: "blob", //告诉服务器想到的响应格式
      headers: {
        "Content-Type": "application/octet-stream",
      },
    })
      .then((res) => {
        if (res) {
          let blob = new Blob([res.data], { type: "application/pdf" });
          const url = URL.createObjectURL(blob);
          loading.value = false;
          pdfUrl.value = url;
          pdfh5.value = new Pdfh5("#pdf-preview", {
            pdfurl: pdfUrl.value,
          });
        } else {
          loading.value = false;
        }
      })
      .catch(function (error) {
        console.log(error);
        loading.value = false;
      });
  } else if (type == "ppt" || type == "pptx") {
    pptUrl.value = "https://view.xdocin.com/view?src=" + props.file.fileUrl;
  } else if (type == "xlsx" || type == "xls") {
    //表格
    request({
      method: "GET",
      url: props.file.fileUrl,
      responseType: "arraybuffer", //告诉服务器想到的响应格式
      headers: {
        "Content-Type":
          "application/vnd.ms-excel;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
      },
    })
      .then((res) => {
        console.log(res, "xls");
        loading.value = false;
        if (res) {
          const workbook = XLSX.read(new Uint8Array(res.data), {
            type: "array",
          });
          const sheetNames = workbook.SheetNames; // 工作表名称集合
          excel.value.workbook = workbook;
          excel.value.sheetNames = sheetNames;
          excel.value.sheetNameActive = sheetNames[0];
          console.log("excel", excel);
          getSheetNameTable(sheetNames[0]);
        }
      })
      .catch(function (error) {
        console.log(error);
        loading.value = false;
      });
  } else if (type == "docx" || type == "doc") {
    request({
      method: "GET",
      url: props.file.fileUrl,
      responseType: "blob", //告诉服务器想到的响应格式
    })
      .then((res) => {
        console.log("DOC", res);
        loading.value = false;
        if (res) {
          // let docx = require("docx-preview");
          docx.renderAsync(res.data, docFile.value, null, {
            className: "docx", // 默认和文档样式类的类名/前缀
            inWrapper: true, // 启用围绕文档内容渲染包装器
            ignoreWidth: false, // 禁止页面渲染宽度
            ignoreHeight: false, // 禁止页面渲染高度
            ignoreFonts: false, // 禁止字体渲染
            breakPages: true, // 在分页符上启用分页
            ignoreLastRenderedPageBreak: true, //禁用lastRenderedPageBreak元素的分页
            experimental: false, //启用实验性功能(制表符停止计算)
            trimXmlDeclaration: true, //如果为真,xml声明将在解析之前从xml文档中删除
            debug: false, // 启用额外的日志记录
          });
          // mammoth.convertToHtml({ arrayBuffer: new Uint8Array(xhr.response) }).then((resultObject) => {
          //   nextTick(() => {
          //     wordText.value = resultObject.value;
          //   });p
          // });
        }
      })
      .catch(function (error) {
        loading.value = false;
      });
  } else if (type === "txt") {
    request({
      method: "GET",
      url: props.file.fileUrl,
    })
      .then((res) => {
        txtText.value = res.data;
        loading.value = false;
      })
      .catch(function (error) {
        console.log(error);
        loading.value = false;
      });
  } else {
    Toast.fail("暂不支持预览该文件类型");
  }
};

const getSheetNameTable = (sheetName) => {
  try {
    // 获取当前工作表的数据
    const worksheet = excel.value.workbook.Sheets[sheetName];
    // 转换为数据  1.json数据有些问题,2.如果是html那么样式需修改
    let htmlData = XLSX.utils.sheet_to_html(worksheet, {
      header: "",
      footer: "",
    });
    htmlData =
      htmlData === ""
        ? htmlData
        : htmlData.replace(
            /<table/,
            '<table class="default-table" border="1px solid #ccc" cellpadding="0" cellspacing="0"'
          );
    // 第一行进行改颜色
    htmlData =
      htmlData === ""
        ? htmlData
        : htmlData.replace(/<tr/, '<tr style="background:#b4c9e8"');
    excel.value.SheetActiveTable = htmlData;
  } catch (e) {
    // 如果工作表没有数据则到这里来处理
    excel.value.SheetActiveTable =
      '<h4 style="text-align: center">' + emptyTips.value + "</h4>";
  }
};

watch(
  () => props.file,
  (newVal, oldVal) => {
    loading.value = true;
    let fileType = props.file.fileName.split(".");
    fileType = fileType[fileType.length - 1];
    console.log(props.file, "watch");
    imgUrl.value = "";
    pdfUrl.value = "";
    init(fileType);
  }
);

defineExpose({
  init,
});
</script>

<template>
  <div class="viewItemFile">
    <van-loading v-if="typeName !== 'pdf' && loading" />
    <div
      class="image"
      v-if="
        typeName.startsWith('JPG') ||
        typeName.startsWith('jpg') ||
        typeName.startsWith('JPEG') ||
        typeName.startsWith('jpeg') ||
        typeName.startsWith('PNG') ||
        typeName.startsWith('png')
      "
    >
      <div>
        <img
          style="display: block; max-width: 100%; margin: 24px auto"
          :src="imgUrl"
          alt=""
        />
      </div>
    </div>
    <div class="docWrap" v-if="typeName == 'docx' || typeName == 'doc'">
      <!-- 预览文件的地方(用于渲染) -->
      <div ref="docFile" class="docPre"></div>
    </div>
    <div class="xlxs-pre" v-if="typeName == 'xlsx' || typeName == 'xls'">
      <div class="tab">
        <a-radio-group
          size="small"
          v-model:value="excel.sheetNameActive"
          @change="getSheetNameTable(excel.sheetNameActive)"
        >
          <a-radio-button
            v-for="(item, index) in excel.sheetNames"
            :key="index"
            :value="item"
            >{{ item }}</a-radio-button
          >
        </a-radio-group>
      </div>
      <div
        style="
          margin-top: 5px;
          border: 1px solid #a0a0a0;
          overflow-x: auto;
          overflow-y: scroll;
        "
      >
        <div v-html="excel.SheetActiveTable" style="padding: 10px 15px"></div>
      </div>
    </div>
    <div v-if="typeName == 'pdf'" style="height: 100%">
      <div id="pdf-preview" />
    </div>
    <div v-if="typeName == 'ppt' || typeName == 'pptx'" style="height: 100%">
      <iframe id="ppt-preview" width="100%" height="auto" :src="pptUrl" />
    </div>
    <div class="text-plain" v-if="typeName === 'txt'">
      <textarea readonly :value="txtText"></textarea>
    </div>
  </div>
</template>

<style lang="less" scoped>
.viewItemFile {
  height: 100%;
  .image {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;

    div {
      height: 600px;
      width: 600px;
    }
  }

  .divContent {
    display: flex;
    align-items: center;
    justify-content: center;
  }
}

.viewItemFile {
  #pdf-preview .pinch-zoom-container {
    height: 100% !important;
  }

  .xlxs-pre {
    height: calc(100vh - 40px);
    padding: 20px;

    .table-html-wrap :deep(table) {
      border-right: 1px solid #e8eaec;
      border-bottom: 1px solid #e8eaec;
      border-collapse: collapse;
      margin: auto;
    }

    .table-html-wrap :deep(table td) {
      border-left: 1px solid #e8eaec;
      border-top: 1px solid #e8eaec;
      white-space: wrap;
      text-align: left;
      min-width: 100px;
      padding: 4px;
    }

    table {
      border-top: 1px solid #ebeef5;
      border-left: 1px solid #ebeef5;
      width: 100%;
      overflow: auto;

      tr {
        height: 44px;
      }

      td {
        min-width: 200px;
        max-width: 400px;
        padding: 4px 8px;
        border-right: 1px solid #ebeef5;
        border-bottom: 1px solid #ebeef5;
      }
    }
  }

  :deep(table) {
    width: 100% !important;
    border-collapse: collapse !important;
    border-spacing: 0 !important;
    text-align: center !important;
    border: 0px !important;
    overflow-x: auto !important;
    overflow-y: scroll !important;
  }

  :deep(table tr td) {
    /* border: 1px solid gray !important; */
    border-right: 1px solid gray !important;
    border-bottom: 1px solid gray !important;
    width: 300px !important;
    height: 33px !important;
  }

  /**整体样式 */
  :deep(.excel-view-container) {
    background-color: #ffffff;
  }

  /**标题样式 */
  :deep(.class4Title) {
    font-size: 22px !important;
    font-weight: bold !important;
    padding: 10px !important;
  }

  /**表格表头样式 */
  :deep(.class4TableTh) {
    /* font-size: 14px !important; */
    font-weight: bold !important;
    padding: 2px !important;
    background-color: #ccc !important;
  }
}

.docWrap {
  width: 100%;

  :deep(.docx-wrapper) {
    background: #ffffff !important;

    :deep(.docx-wrapper section) {
      padding: 8pt 16pt;
      width: 100%;
    }

    :deep(.docx-wrapper > section.docx) {
      box-shadow: 0;
    }
  }
}

html body {
  width: 100%;
  height: 100vh;
  margin: 0;
}
.text-plain {
  width: 100%;
  height: 100%;
  padding-top: 30px;
  textarea {
    width: 100%;
    height: 100%;
    border: none;
  }
  .visible-content {
    height: 100%;
    border: none;
  }
}
</style>

<style scoped>
.docWrap {
  width: 100%;
}

:deep(.docWrap .docx-wrapper) {
  background: #ffffff !important;
  padding: 16px !important;
}

:deep(.docWrap .docx-wrapper > section.docx) {
  padding: 8pt 16pt !important;
  width: 100% !important;
  overflow: scroll !important;
}

:deep(#pdf-preview .pinch-zoom-container) {
  height: auto !important;
}
</style>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.
  • 363.
  • 364.
  • 365.
  • 366.
  • 367.
  • 368.
  • 369.
  • 370.
  • 371.
  • 372.
  • 373.
  • 374.
  • 375.
  • 376.
  • 377.
  • 378.
  • 379.
  • 380.
  • 381.
  • 382.
  • 383.
  • 384.
  • 385.
  • 386.
  • 387.
  • 388.
  • 389.
  • 390.
  • 391.
  • 392.
  • 393.
  • 394.
  • 395.
  • 396.
  • 397.
  • 398.
  • 399.
  • 400.
  • 401.
  • 402.
  • 403.
  • 404.
  • 405.
  • 406.
  • 407.
  • 408.
  • 409.
  • 410.
  • 411.
  • 412.
  • 413.
  • 414.
  • 415.
  • 416.
  • 417.
  • 418.
  • 419.
  • 420.
  • 421.
  • 422.
  • 423.
  • 424.
  • 425.
  • 426.
  • 427.
  • 428.
  • 429.
  • 430.
  • 431.
  • 432.
  • 433.
  • 434.
  • 435.
  • 436.
  • 437.
  • 438.
  • 439.
  • 440.
  • 441.
  • 442.
  • 443.
  • 444.
  • 445.
  • 446.
  • 447.
  • 448.
  • 449.
  • 450.
  • 451.
  • 452.
  • 453.
  • 454.
  • 455.
  • 456.
  • 457.
  • 458.
  • 459.
  • 460.
  • 461.
  • 462.
  • 463.
  • 464.
  • 465.
  • 466.
  • 467.
  • 468.
  • 469.
  • 470.
  • 471.
  • 472.
  • 473.
  • 474.
  • 475.
  • 476.
  • 477.
  • 478.
  • 479.
  • 480.
  • 481.
  • 482.
  • 483.
  • 484.
  • 485.
  • 486.
  • 487.

即可。



原创作者: u_15686949 转载于: https://blog.51cto.com/u_15686949/11306112
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐

  • 浏览量 1114
  • 收藏 0
  • 0

所有评论(0)

查看更多评论 
已为社区贡献1条内容