Compare commits

...

No commits in common. "main" and "v2" have entirely different histories.
main ... v2

25 changed files with 5759 additions and 11067 deletions

27
.gitignore vendored
View File

@ -1,26 +1,21 @@
# Logs .DS_Store
logs node_modules
*.log /dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log* pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files # Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea .idea
.vscode
*.suo *.suo
*.ntvs* *.ntvs*
*.njsproj *.njsproj

View File

@ -1,3 +0,0 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

147
README.md
View File

@ -1,23 +1,29 @@
# FileViewer 项目Vue3 demo # FileViewer 项目Vue2 demo
本demo基于vite+ts+vue3构建如果您需要vue2版本的demo请拉取v2分支。 本demo基于vue-cli+js+vue2.x构建如果您需要vue3版本的demo请前往main分支。
**适用于Vue2 + Webpack本集成方法要求最低Webpack版本为5也就是Vue Cli Service 5.0.0以上当然iframe集成没有任何限制**
> 注意为了版本稳定性在iframe集成的场景下无论是vue2版本还是vue3版本都建议使用file-viewer3以获得最佳性能。 > 注意为了版本稳定性在iframe集成的场景下无论是vue2版本还是vue3版本都建议使用file-viewer3以获得最佳性能。
## 方式一iframe集成推荐 ## 方式一iframe集成推荐
iframe集成是我们最推荐的集成方式可以跳过所有的坑为您的项目快速集成文件预览能力。 iframe集成是我们最推荐的集成方式可以跳过所有的坑为您的项目快速集成文件预览能力。
### 源码准备 ### 源码准备
下载我们的最新版本的file-viewer源码然后执行`npm run build`,或者`yarn build`。 下载我们的最新版本的file-viewer源码然后执行`npm build build`,或者`yarn build`。
### 构建产物集成 ### 构建产物集成
然后将构建后的dist目录拷贝到您项目的public目录下。当然也可以放置到任何项目中。本demo只是演示。 然后将构建后的dist目录拷贝到您项目的public目录下。当然也可以放置到任何项目中。本demo只是演示。
如果您在公网建议您使用我们的cdnhttps://viewer.flyfish.dev以获得高效的访问。 如果您在公网建议您使用我们的cdn
https://viewer.flyfish.dev
以获得高效的访问。
如果您在内网可以完全参照本demo进行实施。 如果您在内网可以完全参照本demo进行实施。
### 添加iframe标签 ### 添加iframe标签
@ -25,7 +31,7 @@ iframe集成是我们最推荐的集成方式可以跳过所有的坑
1. **使用url控制切换推荐** 1. **使用url控制切换推荐**
这种方式是最便捷的实现方式,适合有文件链接的方案。如果你的文件是**流式传输**或者需要用于上传体验,则不适合该方案。 这种方式是最便捷的实现方式,适合有文件链接的方案。如果你的文件是**流式传输**或者需要用于上传体验,则不适合该方案。
2. **使用postMessage发送文件数据** 2. **使用postMessage发送文件数据**
@ -34,61 +40,65 @@ iframe集成是我们最推荐的集成方式可以跳过所有的坑
示例的`IframeViewer.vue`组件实现如下,该组件同时支持两种文件控制方式,您可以直接集成: 示例的`IframeViewer.vue`组件实现如下,该组件同时支持两种文件控制方式,您可以直接集成:
```vue ```vue
<script setup lang="ts"> <template>
import {computed, nextTick, onMounted, ref} from "vue"; <iframe title="文档预览" ref="frame" :src="src" class="iframe-viewer"/>
</template>
const props = defineProps<{ <script>
url?: string,
file?: File,
name?: string,
}>()
// iframe引用 // 查看器的源当前示例为本源指定为location.origin即可
const frame = ref<HTMLIFrameElement>(); const viewerOrigin = location.origin;
// iframe路径指向构建产物这里是/因为放在了public下面
// 如果使用cdn请使用https://viewer.flyfish.dev // iframe路径指向构建产物这里是/因为放在了public下面
const source = '/dist/index.html' // 如果使用cdn请使用https://viewer.flyfish.dev
// 查看器的源当前示例为本源指定为location.origin即可 const source = '/dist/index.html'
const viewerOrigin = location.origin;
// 构建完整url export default {
const src = computed(() => { name: 'IframeViewer',
props: {
url: String,
file: File,
name: String,
},
mounted() {
this.sendFileData();
},
computed: {
// 构建完整url
src() {
// 文件名称,建议传递,提高体验性 // 文件名称,建议传递,提高体验性
const name = props.name || ''; const name = this.name || '';
if (props.url) { if (this.url) {
// 直接拼接url // 直接拼接url
return `${source}?url=${encodeURIComponent(props.url)}&name=${encodeURIComponent(name)}` return `${source}?url=${encodeURIComponent(this.url)}&name=${encodeURIComponent(name)}`
} else if (props.file) { } else if (this.file) {
// 直接拼接来源origin // 直接拼接来源origin
return `${source}?from=${encodeURIComponent(viewerOrigin)}&name=${encodeURIComponent(name)}` return `${source}?from=${encodeURIComponent(viewerOrigin)}&name=${encodeURIComponent(name)}`
} else { } else {
return source; return source;
} }
}) }
},
// 发送文件数据 methods: {
const sendFileData = () => { // 发送文件数据
nextTick(() => { sendFileData() {
const viewer = frame.value; this.nextTick(() => {
if (!viewer || !props.file) return; // iframe引用
viewer.onload = () => viewer.contentWindow?.postMessage(props.file, viewerOrigin); const viewer = this.$refs.frame;
if (!viewer || !this.file) return;
viewer.onload = () => viewer.contentWindow?.postMessage(this.file, viewerOrigin);
}) })
} }
}
onMounted(() => { }
sendFileData();
})
</script> </script>
<template>
<iframe title="文档预览" ref="frame" :src="src" class="iframe-viewer" />
</template>
<style scoped> <style scoped>
.iframe-viewer { .iframe-viewer {
height: calc(100vh - 2px); height: calc(100vh - 2px);
width: 100%; width: 100%;
border: 0 border: 0
} }
</style> </style>
``` ```
@ -109,41 +119,54 @@ onMounted(() => {
以下是示例代码: 以下是示例代码:
`main.ts` `main.js`
```typescript ```javascript
import { createApp } from 'vue' import Vue from 'vue'
import App from './App.vue' import FileViewer from '@flyfish-group/file-viewer'
import FileViewer from '@flyfish-group/file-viewer3'
import '@flyfish-group/file-viewer3/dist/style.css' // 导入样式
import '@flyfish-group/file-viewer/dist/style.css'
Vue.use(FileViewer);
new Vue({
render: h => h(App),
}).$mount('#app')
createApp(App).use(FileViewer).mount('#app')
``` ```
`InnerViewer.vue` `InnerViewer.vue`
```vue ```vue
<script setup lang='ts'>
import {ref} from "vue";
defineProps<{
url: string
}>()
</script>
<template> <template>
<div class='simple-view'> <div class='simple-view'>
<file-viewer :url="url" /> <file-viewer :url="url" />
</div> </div>
</template> </template>
<script>
export default {
name: 'InnerViewer',
props: {
url: String,
},
}
</script>
<style scoped> <style scoped>
.simple-view { .simple-view {
width: 100%; width: 100%;
height: calc(100vh - 2px); height: calc(100vh - 2px);
} }
</style> </style>
``` ```
需要注意的是,内置组件`file-viewer`支持使用data的方式传入文件的二进制数据您可以自行从服务器拉取集成。以下是组件API。
| 属性名 | 类型 | 示例 | 属性描述 |
| ------ | --------------------------- | ---------------------------- | --------------------------------------------------------- |
| file | File \| Blob \| ArrayBuffer | new Blob(...) | 支持文件、二进制blob和arraybuffer数组缓存 |
| url | String | "https://flyfish.dev/1.docx" | 支持任意服务器文件url需要支持跨域访问即存在cors响应头 |

5
babel.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

1
env.d.ts vendored
View File

@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FileViewer3集成示例</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

19
jsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

8617
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,44 @@
{ {
"name": "file-view-demo", "name": "file-view-demo-vue2",
"version": "0.0.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "serve": "vue-cli-service serve",
"build": "run-p type-check build-only", "build": "vue-cli-service build",
"preview": "vite preview", "lint": "vue-cli-service lint"
"build-only": "vite build",
"type-check": "vue-tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@flyfish-group/file-viewer3": "^1.0.2", "@flyfish-group/file-viewer": "^1.0.4",
"vue": "^3.2.47" "core-js": "^3.8.3",
"vue": "^2.6.14"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.14.2", "@babel/core": "^7.12.16",
"@vitejs/plugin-vue": "^4.0.0", "@babel/eslint-parser": "^7.12.16",
"@vitejs/plugin-vue-jsx": "^3.0.0", "@vue/cli-plugin-babel": "~5.0.0",
"@vue/tsconfig": "^0.1.3", "@vue/cli-plugin-eslint": "~5.0.0",
"npm-run-all": "^4.1.5", "@vue/cli-service": "~5.0.0",
"typescript": "~4.8.4", "eslint": "^7.32.0",
"vite": "^4.1.4", "eslint-plugin-vue": "^8.0.3",
"vue-tsc": "^1.2.0" "vue-template-compiler": "^2.6.14"
} },
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
} }

17
public/index.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -1,17 +1,3 @@
<script setup lang="ts">
import {ref} from "vue";
import InnerViewer from './components/InnerViewer.vue'
import IframeViewer from "./components/IframeViewer.vue";
import excel from '@/assets/excel.xlsx?url'
import pdf1 from '@/assets/666.pdf?url'
import pdf2 from '@/assets/888.pdf?url'
// url
const url = ref<string>(excel);
// innerembedded
const mode = ref<string>('inner');
</script>
<template> <template>
<header> <header>
@ -41,6 +27,33 @@ const mode = ref<string>('inner');
</header> </header>
</template> </template>
<script>
import InnerViewer from './components/InnerViewer'
import IframeViewer from "./components/IframeViewer";
//
const excel = 'excel.xlsx';
const pdf1 = '666.pdf';
const pdf2 = '888.pdf';
export default {
name: 'App',
data() {
return {
url: excel,
mode: 'inner',
excel,
pdf1,
pdf2,
}
},
components: {
InnerViewer,
IframeViewer
}
}
</script>
<style scoped> <style scoped>
header { header {
line-height: 1.5; line-height: 1.5;

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

Before

Width:  |  Height:  |  Size: 276 B

View File

@ -1,52 +1,56 @@
<script setup lang="ts"> <template>
import {computed, nextTick, onMounted, ref} from "vue"; <iframe title="文档预览" ref="frame" :src="src" class="iframe-viewer"/>
</template>
const props = defineProps<{ <script>
url?: string,
file?: File, // location.origin
name?: string, const viewerOrigin = location.origin;
}>()
// iframe
const frame = ref<HTMLIFrameElement>();
// iframe/public // iframe/public
// 使cdn使https://viewer.flyfish.dev // 使cdn使https://viewer.flyfish.dev
const source = '/dist/index.html' const source = '/dist/index.html'
// location.origin
const viewerOrigin = location.origin; export default {
// url name: 'IframeViewer',
const src = computed(() => { props: {
url: String,
file: File,
name: String,
},
mounted() {
this.sendFileData();
},
computed: {
// url
src() {
// //
const name = props.name || ''; const name = this.name || '';
if (props.url) { if (this.url) {
// url // url
return `${source}?url=${encodeURIComponent(props.url)}&name=${encodeURIComponent(name)}` return `${source}?url=${encodeURIComponent(this.url)}&name=${encodeURIComponent(name)}`
} else if (props.file) { } else if (this.file) {
// origin // origin
return `${source}?from=${encodeURIComponent(viewerOrigin)}&name=${encodeURIComponent(name)}` return `${source}?from=${encodeURIComponent(viewerOrigin)}&name=${encodeURIComponent(name)}`
} else { } else {
return source; return source;
} }
}) }
},
// methods: {
const sendFileData = () => { //
nextTick(() => { sendFileData() {
const viewer = frame.value; this.nextTick(() => {
if (!viewer || !props.file) return; // iframe
viewer.onload = () => viewer.contentWindow?.postMessage(props.file, viewerOrigin); const viewer = this.$refs.frame;
if (!viewer || !this.file) return;
viewer.onload = () => viewer.contentWindow?.postMessage(this.file, viewerOrigin);
}) })
}
}
} }
onMounted(() => {
sendFileData();
})
</script> </script>
<template>
<iframe title="文档预览" ref="frame" :src="src" class="iframe-viewer" />
</template>
<style scoped> <style scoped>
.iframe-viewer { .iframe-viewer {
height: calc(100vh - 2px); height: calc(100vh - 2px);

View File

@ -1,17 +1,18 @@
<script setup lang='ts'>
import {ref} from "vue";
defineProps<{
url: string
}>()
</script>
<template> <template>
<div class='simple-view'> <div class='simple-view'>
<file-viewer :url="url" /> <file-viewer :url="url" />
</div> </div>
</template> </template>
<script>
export default {
name: 'InnerViewer',
props: {
url: String,
},
}
</script>
<style scoped> <style scoped>
.simple-view { .simple-view {
width: 100%; width: 100%;

14
src/main.js Normal file
View File

@ -0,0 +1,14 @@
import Vue from 'vue'
import App from './App.vue'
import FileViewer from '@flyfish-group/file-viewer'
// 导入样式
import '@flyfish-group/file-viewer/dist/style.css'
Vue.config.productionTip = false
Vue.use(FileViewer);
new Vue({
render: h => h(App),
}).$mount('#app')

View File

@ -1,8 +0,0 @@
import { createApp } from 'vue'
import App from './App.vue'
import FileViewer from '@flyfish-group/file-viewer3'
import './assets/main.css'
import '@flyfish-group/file-viewer3/dist/style.css'
createApp(App).use(FileViewer).mount('#app')

View File

@ -1,16 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

View File

@ -1,8 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
"compilerOptions": {
"composite": true,
"types": ["node"]
}
}

View File

@ -1,21 +0,0 @@
import {fileURLToPath, URL} from 'node:url'
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(), vueJsx()],
base: './',
optimizeDeps: {
include: [
'./node_modules/@flyfish-group/file-viewer3/dist/components',
],
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})

7
vue.config.js Normal file
View File

@ -0,0 +1,7 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
// 我们使用根节点,如果使用其他子目录,请设置具体的路径映射
publicPath: '/',
// 默认转换依赖
transpileDependencies: true
})

7650
yarn.lock

File diff suppressed because it is too large Load Diff