feat: 新增文档预览组件及相关页面
- 新增 DocumentPreview 统一文档预览组件,支持 PDF/Word/Excel/PPT 格式 - 新增文档预览页面和示例页面,支持大文件在线预览 - 新增 OfficeViewer 和 PdfPreview 组件用于 H5 环境 - 新增文档预览工具函数,支持文件类型检测和大小获取 - 配置 ESLint 以支持 Vue 项目,修复相关依赖问题 - 更新 IconFont 组件,添加文档预览所需图标 - 在应用配置中注册新增页面路由
Showing
17 changed files
with
1793 additions
and
4 deletions
| ... | @@ -50,7 +50,11 @@ | ... | @@ -50,7 +50,11 @@ |
| 50 | "Bash(git checkout:*)", | 50 | "Bash(git checkout:*)", |
| 51 | "Bash(python3:*)", | 51 | "Bash(python3:*)", |
| 52 | "Bash(./test-mcp-connection.sh:*)", | 52 | "Bash(./test-mcp-connection.sh:*)", |
| 53 | - "Bash(timeout:*)" | 53 | + "Bash(timeout:*)", |
| 54 | + "Bash(pnpm lint:*)", | ||
| 55 | + "Bash(pnpm dev:weapp:*)", | ||
| 56 | + "Bash(tee:*)", | ||
| 57 | + "Bash(node:*)" | ||
| 54 | ] | 58 | ] |
| 55 | } | 59 | } |
| 56 | } | 60 | } | ... | ... |
.eslintrc.cjs
0 → 100644
| 1 | +module.exports = { | ||
| 2 | + root: true, | ||
| 3 | + env: { | ||
| 4 | + node: true, | ||
| 5 | + es2021: true | ||
| 6 | + }, | ||
| 7 | + globals: { | ||
| 8 | + definePageConfig: 'readonly', | ||
| 9 | + getCurrentPages: 'readonly', | ||
| 10 | + ENABLE_AUTH_MODE: 'readonly', | ||
| 11 | + wx: 'readonly' | ||
| 12 | + }, | ||
| 13 | + extends: ['taro'], | ||
| 14 | + rules: { | ||
| 15 | + 'react-hooks/rules-of-hooks': 'off', | ||
| 16 | + 'react-hooks/exhaustive-deps': 'off', | ||
| 17 | + 'vue/multi-word-component-names': 'off', | ||
| 18 | + 'import/first': 'off', | ||
| 19 | + 'import/newline-after-import': 'off', | ||
| 20 | + 'import/no-duplicates': 'off', | ||
| 21 | + 'import/no-mutable-exports': 'off', | ||
| 22 | + 'no-unused-vars': 'warn' | ||
| 23 | + }, | ||
| 24 | + overrides: [ | ||
| 25 | + { | ||
| 26 | + files: ['**/*.vue'], | ||
| 27 | + extends: ['taro/vue3'], | ||
| 28 | + parser: 'vue-eslint-parser', | ||
| 29 | + parserOptions: { | ||
| 30 | + parser: '@babel/eslint-parser', | ||
| 31 | + requireConfigFile: false, | ||
| 32 | + ecmaVersion: 2021, | ||
| 33 | + sourceType: 'module' | ||
| 34 | + }, | ||
| 35 | + rules: { | ||
| 36 | + 'vue/multi-word-component-names': 'off' | ||
| 37 | + } | ||
| 38 | + }, | ||
| 39 | + { | ||
| 40 | + files: ['**/*.js'], | ||
| 41 | + parser: '@babel/eslint-parser', | ||
| 42 | + parserOptions: { | ||
| 43 | + requireConfigFile: false, | ||
| 44 | + ecmaVersion: 2021, | ||
| 45 | + sourceType: 'module' | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | + ] | ||
| 49 | +} |
| ... | @@ -7,14 +7,19 @@ export {} | ... | @@ -7,14 +7,19 @@ export {} |
| 7 | 7 | ||
| 8 | declare module 'vue' { | 8 | declare module 'vue' { |
| 9 | export interface GlobalComponents { | 9 | export interface GlobalComponents { |
| 10 | + DocumentPreview: typeof import('./src/components/DocumentPreview/index.vue')['default'] | ||
| 10 | IconFont: typeof import('./src/components/IconFont.vue')['default'] | 11 | IconFont: typeof import('./src/components/IconFont.vue')['default'] |
| 11 | IndexNav: typeof import('./src/components/indexNav.vue')['default'] | 12 | IndexNav: typeof import('./src/components/indexNav.vue')['default'] |
| 12 | NavHeader: typeof import('./src/components/NavHeader.vue')['default'] | 13 | NavHeader: typeof import('./src/components/NavHeader.vue')['default'] |
| 13 | NutAvatar: typeof import('@nutui/nutui-taro')['Avatar'] | 14 | NutAvatar: typeof import('@nutui/nutui-taro')['Avatar'] |
| 14 | NutButton: typeof import('@nutui/nutui-taro')['Button'] | 15 | NutButton: typeof import('@nutui/nutui-taro')['Button'] |
| 15 | NutSearchbar: typeof import('@nutui/nutui-taro')['Searchbar'] | 16 | NutSearchbar: typeof import('@nutui/nutui-taro')['Searchbar'] |
| 17 | + NutTabPane: typeof import('@nutui/nutui-taro')['TabPane'] | ||
| 18 | + NutTabs: typeof import('@nutui/nutui-taro')['Tabs'] | ||
| 16 | NutTextarea: typeof import('@nutui/nutui-taro')['Textarea'] | 19 | NutTextarea: typeof import('@nutui/nutui-taro')['Textarea'] |
| 17 | NutUploader: typeof import('@nutui/nutui-taro')['Uploader'] | 20 | NutUploader: typeof import('@nutui/nutui-taro')['Uploader'] |
| 21 | + OfficeViewer: typeof import('./src/components/OfficeViewer.vue')['default'] | ||
| 22 | + PdfPreview: typeof import('./src/components/PdfPreview.vue')['default'] | ||
| 18 | Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] | 23 | Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] |
| 19 | PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] | 24 | PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] |
| 20 | QrCode: typeof import('./src/components/qrCode.vue')['default'] | 25 | QrCode: typeof import('./src/components/qrCode.vue')['default'] | ... | ... |
| ... | @@ -115,3 +115,14 @@ All notable changes to this project will be documented in this file. | ... | @@ -115,3 +115,14 @@ All notable changes to this project will be documented in this file. |
| 115 | - 移除 `src/api/index.js` 中的离线专用接口定义 | 115 | - 移除 `src/api/index.js` 中的离线专用接口定义 |
| 116 | - 更新配置文件,移除 `ENABLE_OFFLINE_MODE` 开关 | 116 | - 更新配置文件,移除 `ENABLE_OFFLINE_MODE` 开关 |
| 117 | - 修复构建告警:移除首页残留的 `ENABLE_OFFLINE_MODE` 与 `@/utils/uiText` 引用 | 117 | - 修复构建告警:移除首页残留的 `ENABLE_OFFLINE_MODE` 与 `@/utils/uiText` 引用 |
| 118 | + | ||
| 119 | +### Fixed | ||
| 120 | +- 修复 ESLint 无法解析 Vue SFC 导致 lint 全量报错:补齐 ESLint 配置与 Vue 解析依赖 | ||
| 121 | +- 修复 eslint-config-taro 在 Vue 项目中触发 React Hooks 规则导致误报的问题 | ||
| 122 | + | ||
| 123 | +### Changed | ||
| 124 | +- 优化 DocumentPreview 小程序端预览策略:无法获取文件大小时默认走在线预览 | ||
| 125 | +- 将 DocumentPreview 小程序端样式单位统一为 rpx | ||
| 126 | + | ||
| 127 | +### Added | ||
| 128 | +- 补全文档预览示例页的 Excel / PPT 在线示例链接 | ... | ... |
| ... | @@ -41,6 +41,7 @@ | ... | @@ -41,6 +41,7 @@ |
| 41 | "license": "MIT", | 41 | "license": "MIT", |
| 42 | "dependencies": { | 42 | "dependencies": { |
| 43 | "@babel/runtime": "^7.7.7", | 43 | "@babel/runtime": "^7.7.7", |
| 44 | + "@nutui/icons-vue": "^0.1.1", | ||
| 44 | "@nutui/icons-vue-taro": "^0.0.9", | 45 | "@nutui/icons-vue-taro": "^0.0.9", |
| 45 | "@nutui/nutui-taro": "^4.3.13", | 46 | "@nutui/nutui-taro": "^4.3.13", |
| 46 | "@tarojs/components": "4.1.9", | 47 | "@tarojs/components": "4.1.9", |
| ... | @@ -80,6 +81,9 @@ | ... | @@ -80,6 +81,9 @@ |
| 80 | "css-loader": "3.4.2", | 81 | "css-loader": "3.4.2", |
| 81 | "eslint": "^8.12.0", | 82 | "eslint": "^8.12.0", |
| 82 | "eslint-config-taro": "4.1.9", | 83 | "eslint-config-taro": "4.1.9", |
| 84 | + "eslint-plugin-react": "^7.33.2", | ||
| 85 | + "eslint-plugin-react-hooks": "^4.4.0", | ||
| 86 | + "eslint-plugin-vue": "^8.0.0", | ||
| 83 | "js-yaml": "^4.1.1", | 87 | "js-yaml": "^4.1.1", |
| 84 | "less": "^4.2.0", | 88 | "less": "^4.2.0", |
| 85 | "postcss": "^8.5.6", | 89 | "postcss": "^8.5.6", |
| ... | @@ -88,6 +92,7 @@ | ... | @@ -88,6 +92,7 @@ |
| 88 | "tailwindcss": "^3.4.0", | 92 | "tailwindcss": "^3.4.0", |
| 89 | "unplugin-vue-components": "^0.26.0", | 93 | "unplugin-vue-components": "^0.26.0", |
| 90 | "vue-loader": "^17.0.0", | 94 | "vue-loader": "^17.0.0", |
| 95 | + "vue-eslint-parser": "^9.0.0", | ||
| 91 | "weapp-tailwindcss": "^4.1.10", | 96 | "weapp-tailwindcss": "^4.1.10", |
| 92 | "webpack": "5.91.0" | 97 | "webpack": "5.91.0" |
| 93 | }, | 98 | }, | ... | ... |
| ... | @@ -11,6 +11,9 @@ importers: | ... | @@ -11,6 +11,9 @@ importers: |
| 11 | '@babel/runtime': | 11 | '@babel/runtime': |
| 12 | specifier: ^7.7.7 | 12 | specifier: ^7.7.7 |
| 13 | version: 7.28.6 | 13 | version: 7.28.6 |
| 14 | + '@nutui/icons-vue': | ||
| 15 | + specifier: ^0.1.1 | ||
| 16 | + version: 0.1.1 | ||
| 14 | '@nutui/icons-vue-taro': | 17 | '@nutui/icons-vue-taro': |
| 15 | specifier: ^0.0.9 | 18 | specifier: ^0.0.9 |
| 16 | version: 0.0.9 | 19 | version: 0.0.9 |
| ... | @@ -122,7 +125,16 @@ importers: | ... | @@ -122,7 +125,16 @@ importers: |
| 122 | version: 8.57.1 | 125 | version: 8.57.1 |
| 123 | eslint-config-taro: | 126 | eslint-config-taro: |
| 124 | specifier: 4.1.9 | 127 | specifier: 4.1.9 |
| 125 | - version: 4.1.9(@babel/core@7.28.6)(eslint@8.57.1)(typescript@5.9.3) | 128 | + version: 4.1.9(@babel/core@7.28.6)(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.37.5(eslint@8.57.1))(eslint-plugin-vue@8.7.1(eslint@8.57.1))(eslint@8.57.1)(typescript@5.9.3) |
| 129 | + eslint-plugin-react: | ||
| 130 | + specifier: ^7.33.2 | ||
| 131 | + version: 7.37.5(eslint@8.57.1) | ||
| 132 | + eslint-plugin-react-hooks: | ||
| 133 | + specifier: ^4.4.0 | ||
| 134 | + version: 4.6.2(eslint@8.57.1) | ||
| 135 | + eslint-plugin-vue: | ||
| 136 | + specifier: ^8.0.0 | ||
| 137 | + version: 8.7.1(eslint@8.57.1) | ||
| 126 | js-yaml: | 138 | js-yaml: |
| 127 | specifier: ^4.1.1 | 139 | specifier: ^4.1.1 |
| 128 | version: 4.1.1 | 140 | version: 4.1.1 |
| ... | @@ -144,6 +156,9 @@ importers: | ... | @@ -144,6 +156,9 @@ importers: |
| 144 | unplugin-vue-components: | 156 | unplugin-vue-components: |
| 145 | specifier: ^0.26.0 | 157 | specifier: ^0.26.0 |
| 146 | version: 0.26.0(@babel/parser@7.28.6)(rollup@3.29.5)(vue@3.5.27(typescript@5.9.3)) | 158 | version: 0.26.0(@babel/parser@7.28.6)(rollup@3.29.5)(vue@3.5.27(typescript@5.9.3)) |
| 159 | + vue-eslint-parser: | ||
| 160 | + specifier: ^9.0.0 | ||
| 161 | + version: 9.4.3(eslint@8.57.1) | ||
| 147 | vue-loader: | 162 | vue-loader: |
| 148 | specifier: ^17.0.0 | 163 | specifier: ^17.0.0 |
| 149 | version: 17.4.2(@vue/compiler-sfc@3.5.27)(vue@3.5.27(typescript@5.9.3))(webpack@5.91.0(@swc/core@1.3.96)) | 164 | version: 17.4.2(@vue/compiler-sfc@3.5.27)(vue@3.5.27(typescript@5.9.3))(webpack@5.91.0(@swc/core@1.3.96)) |
| ... | @@ -1443,6 +1458,9 @@ packages: | ... | @@ -1443,6 +1458,9 @@ packages: |
| 1443 | '@nutui/icons-vue-taro@0.0.9': | 1458 | '@nutui/icons-vue-taro@0.0.9': |
| 1444 | resolution: {integrity: sha512-10VYAtFC+o1X0anGs+y2PgF1NWMeLFz2JVMRw4BWLg6wbtVbYy9wukLxyGhZC6Yf6t39DcwaGVda8paV7K6/Ew==} | 1459 | resolution: {integrity: sha512-10VYAtFC+o1X0anGs+y2PgF1NWMeLFz2JVMRw4BWLg6wbtVbYy9wukLxyGhZC6Yf6t39DcwaGVda8paV7K6/Ew==} |
| 1445 | 1460 | ||
| 1461 | + '@nutui/icons-vue@0.1.1': | ||
| 1462 | + resolution: {integrity: sha512-ekn6R9GNHWNUcV4pxdhQzrI1g1VhZ5zknklXoUJrCzt0RkRRLU0bUXX3ouWT0sl0U3MNJClnXAzZe7iPAvgPtw==} | ||
| 1463 | + | ||
| 1446 | '@nutui/nutui-taro@4.3.14': | 1464 | '@nutui/nutui-taro@4.3.14': |
| 1447 | resolution: {integrity: sha512-MeAipTH2i35H1IiZOMDy2yTuIbhl5qk9x4XYnVh0wgjb+zc3oTaJ9mIds3IleryFI0wxYZJsN4xCHIYSEqLutA==} | 1465 | resolution: {integrity: sha512-MeAipTH2i35H1IiZOMDy2yTuIbhl5qk9x4XYnVh0wgjb+zc3oTaJ9mIds3IleryFI0wxYZJsN4xCHIYSEqLutA==} |
| 1448 | peerDependencies: | 1466 | peerDependencies: |
| ... | @@ -2446,6 +2464,10 @@ packages: | ... | @@ -2446,6 +2464,10 @@ packages: |
| 2446 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} | 2464 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} |
| 2447 | engines: {node: '>=8'} | 2465 | engines: {node: '>=8'} |
| 2448 | 2466 | ||
| 2467 | + array.prototype.findlast@1.2.5: | ||
| 2468 | + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} | ||
| 2469 | + engines: {node: '>= 0.4'} | ||
| 2470 | + | ||
| 2449 | array.prototype.findlastindex@1.2.6: | 2471 | array.prototype.findlastindex@1.2.6: |
| 2450 | resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} | 2472 | resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} |
| 2451 | engines: {node: '>= 0.4'} | 2473 | engines: {node: '>= 0.4'} |
| ... | @@ -2458,6 +2480,10 @@ packages: | ... | @@ -2458,6 +2480,10 @@ packages: |
| 2458 | resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} | 2480 | resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} |
| 2459 | engines: {node: '>= 0.4'} | 2481 | engines: {node: '>= 0.4'} |
| 2460 | 2482 | ||
| 2483 | + array.prototype.tosorted@1.1.4: | ||
| 2484 | + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} | ||
| 2485 | + engines: {node: '>= 0.4'} | ||
| 2486 | + | ||
| 2461 | arraybuffer.prototype.slice@1.0.4: | 2487 | arraybuffer.prototype.slice@1.0.4: |
| 2462 | resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} | 2488 | resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} |
| 2463 | engines: {node: '>= 0.4'} | 2489 | engines: {node: '>= 0.4'} |
| ... | @@ -3352,6 +3378,10 @@ packages: | ... | @@ -3352,6 +3378,10 @@ packages: |
| 3352 | resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} | 3378 | resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} |
| 3353 | engines: {node: '>= 0.4'} | 3379 | engines: {node: '>= 0.4'} |
| 3354 | 3380 | ||
| 3381 | + es-iterator-helpers@1.2.2: | ||
| 3382 | + resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} | ||
| 3383 | + engines: {node: '>= 0.4'} | ||
| 3384 | + | ||
| 3355 | es-module-lexer@0.10.5: | 3385 | es-module-lexer@0.10.5: |
| 3356 | resolution: {integrity: sha512-+7IwY/kiGAacQfY+YBhKMvEmyAJnw5grTUgjG85Pe7vcUI/6b7pZjZG8nQ7+48YhzEAEqrEgD2dCz/JIK+AYvw==} | 3386 | resolution: {integrity: sha512-+7IwY/kiGAacQfY+YBhKMvEmyAJnw5grTUgjG85Pe7vcUI/6b7pZjZG8nQ7+48YhzEAEqrEgD2dCz/JIK+AYvw==} |
| 3357 | 3387 | ||
| ... | @@ -3454,6 +3484,24 @@ packages: | ... | @@ -3454,6 +3484,24 @@ packages: |
| 3454 | '@typescript-eslint/parser': | 3484 | '@typescript-eslint/parser': |
| 3455 | optional: true | 3485 | optional: true |
| 3456 | 3486 | ||
| 3487 | + eslint-plugin-react-hooks@4.6.2: | ||
| 3488 | + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} | ||
| 3489 | + engines: {node: '>=10'} | ||
| 3490 | + peerDependencies: | ||
| 3491 | + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 | ||
| 3492 | + | ||
| 3493 | + eslint-plugin-react@7.37.5: | ||
| 3494 | + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} | ||
| 3495 | + engines: {node: '>=4'} | ||
| 3496 | + peerDependencies: | ||
| 3497 | + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 | ||
| 3498 | + | ||
| 3499 | + eslint-plugin-vue@8.7.1: | ||
| 3500 | + resolution: {integrity: sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg==} | ||
| 3501 | + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} | ||
| 3502 | + peerDependencies: | ||
| 3503 | + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 | ||
| 3504 | + | ||
| 3457 | eslint-scope@5.1.1: | 3505 | eslint-scope@5.1.1: |
| 3458 | resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} | 3506 | resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} |
| 3459 | engines: {node: '>=8.0.0'} | 3507 | engines: {node: '>=8.0.0'} |
| ... | @@ -3462,6 +3510,12 @@ packages: | ... | @@ -3462,6 +3510,12 @@ packages: |
| 3462 | resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} | 3510 | resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} |
| 3463 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} | 3511 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} |
| 3464 | 3512 | ||
| 3513 | + eslint-utils@3.0.0: | ||
| 3514 | + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} | ||
| 3515 | + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} | ||
| 3516 | + peerDependencies: | ||
| 3517 | + eslint: '>=5' | ||
| 3518 | + | ||
| 3465 | eslint-visitor-keys@2.1.0: | 3519 | eslint-visitor-keys@2.1.0: |
| 3466 | resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} | 3520 | resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} |
| 3467 | engines: {node: '>=10'} | 3521 | engines: {node: '>=10'} |
| ... | @@ -4280,6 +4334,10 @@ packages: | ... | @@ -4280,6 +4334,10 @@ packages: |
| 4280 | resolution: {integrity: sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==} | 4334 | resolution: {integrity: sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==} |
| 4281 | engines: {node: '>= 4'} | 4335 | engines: {node: '>= 4'} |
| 4282 | 4336 | ||
| 4337 | + iterator.prototype@1.1.5: | ||
| 4338 | + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} | ||
| 4339 | + engines: {node: '>= 0.4'} | ||
| 4340 | + | ||
| 4283 | j-component@1.4.9: | 4341 | j-component@1.4.9: |
| 4284 | resolution: {integrity: sha512-7TaTylECTW4sRaDLaj463sTj9BK6/3rSD67um47ypLPwtZW3wPwynCQ9sdnEJmTIw9Jfy2ZLKWiSDRdaINv50w==} | 4342 | resolution: {integrity: sha512-7TaTylECTW4sRaDLaj463sTj9BK6/3rSD67um47ypLPwtZW3wPwynCQ9sdnEJmTIw9Jfy2ZLKWiSDRdaINv50w==} |
| 4285 | 4343 | ||
| ... | @@ -4373,6 +4431,10 @@ packages: | ... | @@ -4373,6 +4431,10 @@ packages: |
| 4373 | jsonp-retry@1.0.3: | 4431 | jsonp-retry@1.0.3: |
| 4374 | resolution: {integrity: sha512-/jmE9+shtKP+oIt2AWO9Wx+C27NTGpLCEw4QHOqpoV2X6ta374HE9C+EEdgu8r3iLKgFMx7u5j0mCwxWN8UdlA==} | 4432 | resolution: {integrity: sha512-/jmE9+shtKP+oIt2AWO9Wx+C27NTGpLCEw4QHOqpoV2X6ta374HE9C+EEdgu8r3iLKgFMx7u5j0mCwxWN8UdlA==} |
| 4375 | 4433 | ||
| 4434 | + jsx-ast-utils@3.3.5: | ||
| 4435 | + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} | ||
| 4436 | + engines: {node: '>=4.0'} | ||
| 4437 | + | ||
| 4376 | keyv@3.0.0: | 4438 | keyv@3.0.0: |
| 4377 | resolution: {integrity: sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==} | 4439 | resolution: {integrity: sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==} |
| 4378 | 4440 | ||
| ... | @@ -4642,6 +4704,10 @@ packages: | ... | @@ -4642,6 +4704,10 @@ packages: |
| 4642 | resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} | 4704 | resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} |
| 4643 | engines: {node: '>= 0.6.0'} | 4705 | engines: {node: '>= 0.6.0'} |
| 4644 | 4706 | ||
| 4707 | + loose-envify@1.4.0: | ||
| 4708 | + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} | ||
| 4709 | + hasBin: true | ||
| 4710 | + | ||
| 4645 | lower-case@1.1.4: | 4711 | lower-case@1.1.4: |
| 4646 | resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==} | 4712 | resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==} |
| 4647 | 4713 | ||
| ... | @@ -4925,6 +4991,10 @@ packages: | ... | @@ -4925,6 +4991,10 @@ packages: |
| 4925 | resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} | 4991 | resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} |
| 4926 | engines: {node: '>= 0.4'} | 4992 | engines: {node: '>= 0.4'} |
| 4927 | 4993 | ||
| 4994 | + object.entries@1.1.9: | ||
| 4995 | + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} | ||
| 4996 | + engines: {node: '>= 0.4'} | ||
| 4997 | + | ||
| 4928 | object.fromentries@2.0.8: | 4998 | object.fromentries@2.0.8: |
| 4929 | resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} | 4999 | resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} |
| 4930 | engines: {node: '>= 0.4'} | 5000 | engines: {node: '>= 0.4'} |
| ... | @@ -5687,6 +5757,9 @@ packages: | ... | @@ -5687,6 +5757,9 @@ packages: |
| 5687 | promise-polyfill@7.1.2: | 5757 | promise-polyfill@7.1.2: |
| 5688 | resolution: {integrity: sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ==} | 5758 | resolution: {integrity: sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ==} |
| 5689 | 5759 | ||
| 5760 | + prop-types@15.8.1: | ||
| 5761 | + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} | ||
| 5762 | + | ||
| 5690 | property-expr@2.0.6: | 5763 | property-expr@2.0.6: |
| 5691 | resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} | 5764 | resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} |
| 5692 | 5765 | ||
| ... | @@ -5757,6 +5830,9 @@ packages: | ... | @@ -5757,6 +5830,9 @@ packages: |
| 5757 | resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} | 5830 | resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} |
| 5758 | hasBin: true | 5831 | hasBin: true |
| 5759 | 5832 | ||
| 5833 | + react-is@16.13.1: | ||
| 5834 | + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} | ||
| 5835 | + | ||
| 5760 | react-is@17.0.2: | 5836 | react-is@17.0.2: |
| 5761 | resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} | 5837 | resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} |
| 5762 | 5838 | ||
| ... | @@ -5866,6 +5942,10 @@ packages: | ... | @@ -5866,6 +5942,10 @@ packages: |
| 5866 | engines: {node: '>= 0.4'} | 5942 | engines: {node: '>= 0.4'} |
| 5867 | hasBin: true | 5943 | hasBin: true |
| 5868 | 5944 | ||
| 5945 | + resolve@2.0.0-next.5: | ||
| 5946 | + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} | ||
| 5947 | + hasBin: true | ||
| 5948 | + | ||
| 5869 | responselike@1.0.2: | 5949 | responselike@1.0.2: |
| 5870 | resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} | 5950 | resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} |
| 5871 | 5951 | ||
| ... | @@ -6192,6 +6272,13 @@ packages: | ... | @@ -6192,6 +6272,13 @@ packages: |
| 6192 | string.fromcodepoint@0.2.1: | 6272 | string.fromcodepoint@0.2.1: |
| 6193 | resolution: {integrity: sha512-n69H31OnxSGSZyZbgBlvYIXlrMhJQ0dQAX1js1QDhpaUH6zmU3QYlj07bCwCNlPOu3oRXIubGPl2gDGnHsiCqg==} | 6273 | resolution: {integrity: sha512-n69H31OnxSGSZyZbgBlvYIXlrMhJQ0dQAX1js1QDhpaUH6zmU3QYlj07bCwCNlPOu3oRXIubGPl2gDGnHsiCqg==} |
| 6194 | 6274 | ||
| 6275 | + string.prototype.matchall@4.0.12: | ||
| 6276 | + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} | ||
| 6277 | + engines: {node: '>= 0.4'} | ||
| 6278 | + | ||
| 6279 | + string.prototype.repeat@1.0.0: | ||
| 6280 | + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} | ||
| 6281 | + | ||
| 6195 | string.prototype.trim@1.2.10: | 6282 | string.prototype.trim@1.2.10: |
| 6196 | resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} | 6283 | resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} |
| 6197 | engines: {node: '>= 0.4'} | 6284 | engines: {node: '>= 0.4'} |
| ... | @@ -6630,6 +6717,18 @@ packages: | ... | @@ -6630,6 +6717,18 @@ packages: |
| 6630 | engines: {node: '>=6.0'} | 6717 | engines: {node: '>=6.0'} |
| 6631 | hasBin: true | 6718 | hasBin: true |
| 6632 | 6719 | ||
| 6720 | + vue-eslint-parser@8.3.0: | ||
| 6721 | + resolution: {integrity: sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==} | ||
| 6722 | + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} | ||
| 6723 | + peerDependencies: | ||
| 6724 | + eslint: '>=6.0.0' | ||
| 6725 | + | ||
| 6726 | + vue-eslint-parser@9.4.3: | ||
| 6727 | + resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} | ||
| 6728 | + engines: {node: ^14.17.0 || >=16.0.0} | ||
| 6729 | + peerDependencies: | ||
| 6730 | + eslint: '>=6.0.0' | ||
| 6731 | + | ||
| 6633 | vue-loader@17.4.2: | 6732 | vue-loader@17.4.2: |
| 6634 | resolution: {integrity: sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==} | 6733 | resolution: {integrity: sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==} |
| 6635 | peerDependencies: | 6734 | peerDependencies: |
| ... | @@ -8220,6 +8319,8 @@ snapshots: | ... | @@ -8220,6 +8319,8 @@ snapshots: |
| 8220 | 8319 | ||
| 8221 | '@nutui/icons-vue-taro@0.0.9': {} | 8320 | '@nutui/icons-vue-taro@0.0.9': {} |
| 8222 | 8321 | ||
| 8322 | + '@nutui/icons-vue@0.1.1': {} | ||
| 8323 | + | ||
| 8223 | '@nutui/nutui-taro@4.3.14(unplugin-vue-components@0.26.0(@babel/parser@7.28.6)(rollup@3.29.5)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))': | 8324 | '@nutui/nutui-taro@4.3.14(unplugin-vue-components@0.26.0(@babel/parser@7.28.6)(rollup@3.29.5)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))': |
| 8224 | dependencies: | 8325 | dependencies: |
| 8225 | '@nutui/icons-vue-taro': 0.0.9 | 8326 | '@nutui/icons-vue-taro': 0.0.9 |
| ... | @@ -9432,6 +9533,15 @@ snapshots: | ... | @@ -9432,6 +9533,15 @@ snapshots: |
| 9432 | 9533 | ||
| 9433 | array-union@2.1.0: {} | 9534 | array-union@2.1.0: {} |
| 9434 | 9535 | ||
| 9536 | + array.prototype.findlast@1.2.5: | ||
| 9537 | + dependencies: | ||
| 9538 | + call-bind: 1.0.8 | ||
| 9539 | + define-properties: 1.2.1 | ||
| 9540 | + es-abstract: 1.24.1 | ||
| 9541 | + es-errors: 1.3.0 | ||
| 9542 | + es-object-atoms: 1.1.1 | ||
| 9543 | + es-shim-unscopables: 1.1.0 | ||
| 9544 | + | ||
| 9435 | array.prototype.findlastindex@1.2.6: | 9545 | array.prototype.findlastindex@1.2.6: |
| 9436 | dependencies: | 9546 | dependencies: |
| 9437 | call-bind: 1.0.8 | 9547 | call-bind: 1.0.8 |
| ... | @@ -9456,6 +9566,14 @@ snapshots: | ... | @@ -9456,6 +9566,14 @@ snapshots: |
| 9456 | es-abstract: 1.24.1 | 9566 | es-abstract: 1.24.1 |
| 9457 | es-shim-unscopables: 1.1.0 | 9567 | es-shim-unscopables: 1.1.0 |
| 9458 | 9568 | ||
| 9569 | + array.prototype.tosorted@1.1.4: | ||
| 9570 | + dependencies: | ||
| 9571 | + call-bind: 1.0.8 | ||
| 9572 | + define-properties: 1.2.1 | ||
| 9573 | + es-abstract: 1.24.1 | ||
| 9574 | + es-errors: 1.3.0 | ||
| 9575 | + es-shim-unscopables: 1.1.0 | ||
| 9576 | + | ||
| 9459 | arraybuffer.prototype.slice@1.0.4: | 9577 | arraybuffer.prototype.slice@1.0.4: |
| 9460 | dependencies: | 9578 | dependencies: |
| 9461 | array-buffer-byte-length: 1.0.2 | 9579 | array-buffer-byte-length: 1.0.2 |
| ... | @@ -10513,6 +10631,25 @@ snapshots: | ... | @@ -10513,6 +10631,25 @@ snapshots: |
| 10513 | 10631 | ||
| 10514 | es-errors@1.3.0: {} | 10632 | es-errors@1.3.0: {} |
| 10515 | 10633 | ||
| 10634 | + es-iterator-helpers@1.2.2: | ||
| 10635 | + dependencies: | ||
| 10636 | + call-bind: 1.0.8 | ||
| 10637 | + call-bound: 1.0.4 | ||
| 10638 | + define-properties: 1.2.1 | ||
| 10639 | + es-abstract: 1.24.1 | ||
| 10640 | + es-errors: 1.3.0 | ||
| 10641 | + es-set-tostringtag: 2.1.0 | ||
| 10642 | + function-bind: 1.1.2 | ||
| 10643 | + get-intrinsic: 1.3.0 | ||
| 10644 | + globalthis: 1.0.4 | ||
| 10645 | + gopd: 1.2.0 | ||
| 10646 | + has-property-descriptors: 1.0.2 | ||
| 10647 | + has-proto: 1.2.0 | ||
| 10648 | + has-symbols: 1.1.0 | ||
| 10649 | + internal-slot: 1.1.0 | ||
| 10650 | + iterator.prototype: 1.1.5 | ||
| 10651 | + safe-array-concat: 1.1.3 | ||
| 10652 | + | ||
| 10516 | es-module-lexer@0.10.5: {} | 10653 | es-module-lexer@0.10.5: {} |
| 10517 | 10654 | ||
| 10518 | es-module-lexer@1.7.0: {} | 10655 | es-module-lexer@1.7.0: {} |
| ... | @@ -10609,13 +10746,17 @@ snapshots: | ... | @@ -10609,13 +10746,17 @@ snapshots: |
| 10609 | 10746 | ||
| 10610 | escape-string-regexp@4.0.0: {} | 10747 | escape-string-regexp@4.0.0: {} |
| 10611 | 10748 | ||
| 10612 | - eslint-config-taro@4.1.9(@babel/core@7.28.6)(eslint@8.57.1)(typescript@5.9.3): | 10749 | + eslint-config-taro@4.1.9(@babel/core@7.28.6)(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.37.5(eslint@8.57.1))(eslint-plugin-vue@8.7.1(eslint@8.57.1))(eslint@8.57.1)(typescript@5.9.3): |
| 10613 | dependencies: | 10750 | dependencies: |
| 10614 | '@babel/eslint-parser': 7.28.6(@babel/core@7.28.6)(eslint@8.57.1) | 10751 | '@babel/eslint-parser': 7.28.6(@babel/core@7.28.6)(eslint@8.57.1) |
| 10615 | '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) | 10752 | '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) |
| 10616 | '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) | 10753 | '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) |
| 10617 | eslint: 8.57.1 | 10754 | eslint: 8.57.1 |
| 10618 | eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1) | 10755 | eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1) |
| 10756 | + optionalDependencies: | ||
| 10757 | + eslint-plugin-react: 7.37.5(eslint@8.57.1) | ||
| 10758 | + eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) | ||
| 10759 | + eslint-plugin-vue: 8.7.1(eslint@8.57.1) | ||
| 10619 | transitivePeerDependencies: | 10760 | transitivePeerDependencies: |
| 10620 | - '@babel/core' | 10761 | - '@babel/core' |
| 10621 | - eslint-import-resolver-typescript | 10762 | - eslint-import-resolver-typescript |
| ... | @@ -10670,6 +10811,44 @@ snapshots: | ... | @@ -10670,6 +10811,44 @@ snapshots: |
| 10670 | - eslint-import-resolver-webpack | 10811 | - eslint-import-resolver-webpack |
| 10671 | - supports-color | 10812 | - supports-color |
| 10672 | 10813 | ||
| 10814 | + eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): | ||
| 10815 | + dependencies: | ||
| 10816 | + eslint: 8.57.1 | ||
| 10817 | + | ||
| 10818 | + eslint-plugin-react@7.37.5(eslint@8.57.1): | ||
| 10819 | + dependencies: | ||
| 10820 | + array-includes: 3.1.9 | ||
| 10821 | + array.prototype.findlast: 1.2.5 | ||
| 10822 | + array.prototype.flatmap: 1.3.3 | ||
| 10823 | + array.prototype.tosorted: 1.1.4 | ||
| 10824 | + doctrine: 2.1.0 | ||
| 10825 | + es-iterator-helpers: 1.2.2 | ||
| 10826 | + eslint: 8.57.1 | ||
| 10827 | + estraverse: 5.3.0 | ||
| 10828 | + hasown: 2.0.2 | ||
| 10829 | + jsx-ast-utils: 3.3.5 | ||
| 10830 | + minimatch: 3.1.2 | ||
| 10831 | + object.entries: 1.1.9 | ||
| 10832 | + object.fromentries: 2.0.8 | ||
| 10833 | + object.values: 1.2.1 | ||
| 10834 | + prop-types: 15.8.1 | ||
| 10835 | + resolve: 2.0.0-next.5 | ||
| 10836 | + semver: 6.3.1 | ||
| 10837 | + string.prototype.matchall: 4.0.12 | ||
| 10838 | + string.prototype.repeat: 1.0.0 | ||
| 10839 | + | ||
| 10840 | + eslint-plugin-vue@8.7.1(eslint@8.57.1): | ||
| 10841 | + dependencies: | ||
| 10842 | + eslint: 8.57.1 | ||
| 10843 | + eslint-utils: 3.0.0(eslint@8.57.1) | ||
| 10844 | + natural-compare: 1.4.0 | ||
| 10845 | + nth-check: 2.1.1 | ||
| 10846 | + postcss-selector-parser: 6.1.2 | ||
| 10847 | + semver: 7.7.3 | ||
| 10848 | + vue-eslint-parser: 8.3.0(eslint@8.57.1) | ||
| 10849 | + transitivePeerDependencies: | ||
| 10850 | + - supports-color | ||
| 10851 | + | ||
| 10673 | eslint-scope@5.1.1: | 10852 | eslint-scope@5.1.1: |
| 10674 | dependencies: | 10853 | dependencies: |
| 10675 | esrecurse: 4.3.0 | 10854 | esrecurse: 4.3.0 |
| ... | @@ -10680,6 +10859,11 @@ snapshots: | ... | @@ -10680,6 +10859,11 @@ snapshots: |
| 10680 | esrecurse: 4.3.0 | 10859 | esrecurse: 4.3.0 |
| 10681 | estraverse: 5.3.0 | 10860 | estraverse: 5.3.0 |
| 10682 | 10861 | ||
| 10862 | + eslint-utils@3.0.0(eslint@8.57.1): | ||
| 10863 | + dependencies: | ||
| 10864 | + eslint: 8.57.1 | ||
| 10865 | + eslint-visitor-keys: 2.1.0 | ||
| 10866 | + | ||
| 10683 | eslint-visitor-keys@2.1.0: {} | 10867 | eslint-visitor-keys@2.1.0: {} |
| 10684 | 10868 | ||
| 10685 | eslint-visitor-keys@3.4.3: {} | 10869 | eslint-visitor-keys@3.4.3: {} |
| ... | @@ -11648,6 +11832,15 @@ snapshots: | ... | @@ -11648,6 +11832,15 @@ snapshots: |
| 11648 | has-to-string-tag-x: 1.4.1 | 11832 | has-to-string-tag-x: 1.4.1 |
| 11649 | is-object: 1.0.2 | 11833 | is-object: 1.0.2 |
| 11650 | 11834 | ||
| 11835 | + iterator.prototype@1.1.5: | ||
| 11836 | + dependencies: | ||
| 11837 | + define-data-property: 1.1.4 | ||
| 11838 | + es-object-atoms: 1.1.1 | ||
| 11839 | + get-intrinsic: 1.3.0 | ||
| 11840 | + get-proto: 1.0.1 | ||
| 11841 | + has-symbols: 1.1.0 | ||
| 11842 | + set-function-name: 2.0.2 | ||
| 11843 | + | ||
| 11651 | j-component@1.4.9: | 11844 | j-component@1.4.9: |
| 11652 | dependencies: | 11845 | dependencies: |
| 11653 | expr-parser: 1.0.0 | 11846 | expr-parser: 1.0.0 |
| ... | @@ -11770,6 +11963,13 @@ snapshots: | ... | @@ -11770,6 +11963,13 @@ snapshots: |
| 11770 | dependencies: | 11963 | dependencies: |
| 11771 | object-assign: 4.1.1 | 11964 | object-assign: 4.1.1 |
| 11772 | 11965 | ||
| 11966 | + jsx-ast-utils@3.3.5: | ||
| 11967 | + dependencies: | ||
| 11968 | + array-includes: 3.1.9 | ||
| 11969 | + array.prototype.flat: 1.3.3 | ||
| 11970 | + object.assign: 4.1.7 | ||
| 11971 | + object.values: 1.2.1 | ||
| 11972 | + | ||
| 11773 | keyv@3.0.0: | 11973 | keyv@3.0.0: |
| 11774 | dependencies: | 11974 | dependencies: |
| 11775 | json-buffer: 3.0.0 | 11975 | json-buffer: 3.0.0 |
| ... | @@ -11997,6 +12197,10 @@ snapshots: | ... | @@ -11997,6 +12197,10 @@ snapshots: |
| 11997 | 12197 | ||
| 11998 | loglevel@1.9.2: {} | 12198 | loglevel@1.9.2: {} |
| 11999 | 12199 | ||
| 12200 | + loose-envify@1.4.0: | ||
| 12201 | + dependencies: | ||
| 12202 | + js-tokens: 4.0.0 | ||
| 12203 | + | ||
| 12000 | lower-case@1.1.4: {} | 12204 | lower-case@1.1.4: {} |
| 12001 | 12205 | ||
| 12002 | lower-case@2.0.2: | 12206 | lower-case@2.0.2: |
| ... | @@ -12246,6 +12450,13 @@ snapshots: | ... | @@ -12246,6 +12450,13 @@ snapshots: |
| 12246 | has-symbols: 1.1.0 | 12450 | has-symbols: 1.1.0 |
| 12247 | object-keys: 1.1.1 | 12451 | object-keys: 1.1.1 |
| 12248 | 12452 | ||
| 12453 | + object.entries@1.1.9: | ||
| 12454 | + dependencies: | ||
| 12455 | + call-bind: 1.0.8 | ||
| 12456 | + call-bound: 1.0.4 | ||
| 12457 | + define-properties: 1.2.1 | ||
| 12458 | + es-object-atoms: 1.1.1 | ||
| 12459 | + | ||
| 12249 | object.fromentries@2.0.8: | 12460 | object.fromentries@2.0.8: |
| 12250 | dependencies: | 12461 | dependencies: |
| 12251 | call-bind: 1.0.8 | 12462 | call-bind: 1.0.8 |
| ... | @@ -13031,6 +13242,12 @@ snapshots: | ... | @@ -13031,6 +13242,12 @@ snapshots: |
| 13031 | 13242 | ||
| 13032 | promise-polyfill@7.1.2: {} | 13243 | promise-polyfill@7.1.2: {} |
| 13033 | 13244 | ||
| 13245 | + prop-types@15.8.1: | ||
| 13246 | + dependencies: | ||
| 13247 | + loose-envify: 1.4.0 | ||
| 13248 | + object-assign: 4.1.1 | ||
| 13249 | + react-is: 16.13.1 | ||
| 13250 | + | ||
| 13034 | property-expr@2.0.6: {} | 13251 | property-expr@2.0.6: {} |
| 13035 | 13252 | ||
| 13036 | proto-list@1.2.4: {} | 13253 | proto-list@1.2.4: {} |
| ... | @@ -13109,6 +13326,8 @@ snapshots: | ... | @@ -13109,6 +13326,8 @@ snapshots: |
| 13109 | minimist: 1.2.8 | 13326 | minimist: 1.2.8 |
| 13110 | strip-json-comments: 2.0.1 | 13327 | strip-json-comments: 2.0.1 |
| 13111 | 13328 | ||
| 13329 | + react-is@16.13.1: {} | ||
| 13330 | + | ||
| 13112 | react-is@17.0.2: {} | 13331 | react-is@17.0.2: {} |
| 13113 | 13332 | ||
| 13114 | react@19.2.4: {} | 13333 | react@19.2.4: {} |
| ... | @@ -13232,6 +13451,12 @@ snapshots: | ... | @@ -13232,6 +13451,12 @@ snapshots: |
| 13232 | path-parse: 1.0.7 | 13451 | path-parse: 1.0.7 |
| 13233 | supports-preserve-symlinks-flag: 1.0.0 | 13452 | supports-preserve-symlinks-flag: 1.0.0 |
| 13234 | 13453 | ||
| 13454 | + resolve@2.0.0-next.5: | ||
| 13455 | + dependencies: | ||
| 13456 | + is-core-module: 2.16.1 | ||
| 13457 | + path-parse: 1.0.7 | ||
| 13458 | + supports-preserve-symlinks-flag: 1.0.0 | ||
| 13459 | + | ||
| 13235 | responselike@1.0.2: | 13460 | responselike@1.0.2: |
| 13236 | dependencies: | 13461 | dependencies: |
| 13237 | lowercase-keys: 1.0.1 | 13462 | lowercase-keys: 1.0.1 |
| ... | @@ -13601,6 +13826,27 @@ snapshots: | ... | @@ -13601,6 +13826,27 @@ snapshots: |
| 13601 | 13826 | ||
| 13602 | string.fromcodepoint@0.2.1: {} | 13827 | string.fromcodepoint@0.2.1: {} |
| 13603 | 13828 | ||
| 13829 | + string.prototype.matchall@4.0.12: | ||
| 13830 | + dependencies: | ||
| 13831 | + call-bind: 1.0.8 | ||
| 13832 | + call-bound: 1.0.4 | ||
| 13833 | + define-properties: 1.2.1 | ||
| 13834 | + es-abstract: 1.24.1 | ||
| 13835 | + es-errors: 1.3.0 | ||
| 13836 | + es-object-atoms: 1.1.1 | ||
| 13837 | + get-intrinsic: 1.3.0 | ||
| 13838 | + gopd: 1.2.0 | ||
| 13839 | + has-symbols: 1.1.0 | ||
| 13840 | + internal-slot: 1.1.0 | ||
| 13841 | + regexp.prototype.flags: 1.5.4 | ||
| 13842 | + set-function-name: 2.0.2 | ||
| 13843 | + side-channel: 1.1.0 | ||
| 13844 | + | ||
| 13845 | + string.prototype.repeat@1.0.0: | ||
| 13846 | + dependencies: | ||
| 13847 | + define-properties: 1.2.1 | ||
| 13848 | + es-abstract: 1.24.1 | ||
| 13849 | + | ||
| 13604 | string.prototype.trim@1.2.10: | 13850 | string.prototype.trim@1.2.10: |
| 13605 | dependencies: | 13851 | dependencies: |
| 13606 | call-bind: 1.0.8 | 13852 | call-bind: 1.0.8 |
| ... | @@ -14074,6 +14320,32 @@ snapshots: | ... | @@ -14074,6 +14320,32 @@ snapshots: |
| 14074 | acorn: 8.15.0 | 14320 | acorn: 8.15.0 |
| 14075 | acorn-walk: 8.3.4 | 14321 | acorn-walk: 8.3.4 |
| 14076 | 14322 | ||
| 14323 | + vue-eslint-parser@8.3.0(eslint@8.57.1): | ||
| 14324 | + dependencies: | ||
| 14325 | + debug: 4.4.3 | ||
| 14326 | + eslint: 8.57.1 | ||
| 14327 | + eslint-scope: 7.2.2 | ||
| 14328 | + eslint-visitor-keys: 3.4.3 | ||
| 14329 | + espree: 9.6.1 | ||
| 14330 | + esquery: 1.7.0 | ||
| 14331 | + lodash: 4.17.23 | ||
| 14332 | + semver: 7.7.3 | ||
| 14333 | + transitivePeerDependencies: | ||
| 14334 | + - supports-color | ||
| 14335 | + | ||
| 14336 | + vue-eslint-parser@9.4.3(eslint@8.57.1): | ||
| 14337 | + dependencies: | ||
| 14338 | + debug: 4.4.3 | ||
| 14339 | + eslint: 8.57.1 | ||
| 14340 | + eslint-scope: 7.2.2 | ||
| 14341 | + eslint-visitor-keys: 3.4.3 | ||
| 14342 | + espree: 9.6.1 | ||
| 14343 | + esquery: 1.7.0 | ||
| 14344 | + lodash: 4.17.23 | ||
| 14345 | + semver: 7.7.3 | ||
| 14346 | + transitivePeerDependencies: | ||
| 14347 | + - supports-color | ||
| 14348 | + | ||
| 14077 | vue-loader@17.4.2(@vue/compiler-sfc@3.5.27)(vue@3.5.27(typescript@5.9.3))(webpack@5.91.0(@swc/core@1.3.96)): | 14349 | vue-loader@17.4.2(@vue/compiler-sfc@3.5.27)(vue@3.5.27(typescript@5.9.3))(webpack@5.91.0(@swc/core@1.3.96)): |
| 14078 | dependencies: | 14350 | dependencies: |
| 14079 | chalk: 4.1.2 | 14351 | chalk: 4.1.2 | ... | ... |
| 1 | /* | 1 | /* |
| 2 | * @Date: 2025-06-28 10:33:00 | 2 | * @Date: 2025-06-28 10:33:00 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2026-01-29 22:47:02 | 4 | + * @LastEditTime: 2026-01-30 10:07:28 |
| 5 | * @FilePath: /manulife-weapp/src/app.config.js | 5 | * @FilePath: /manulife-weapp/src/app.config.js |
| 6 | * @Description: 小程序配置文件 | 6 | * @Description: 小程序配置文件 |
| 7 | */ | 7 | */ |
| ... | @@ -9,6 +9,8 @@ const pages = [ | ... | @@ -9,6 +9,8 @@ const pages = [ |
| 9 | 'pages/index/index', | 9 | 'pages/index/index', |
| 10 | 'pages/search/index', | 10 | 'pages/search/index', |
| 11 | 'pages/webview/index', | 11 | 'pages/webview/index', |
| 12 | + 'pages/document-preview/index', | ||
| 13 | + 'pages/document-demo/index', | ||
| 12 | 'pages/auth/index', | 14 | 'pages/auth/index', |
| 13 | 'pages/onboarding/index', | 15 | 'pages/onboarding/index', |
| 14 | 'pages/family-office/index', | 16 | 'pages/family-office/index', | ... | ... |
src/components/DocumentPreview/README.md
0 → 100644
| 1 | +# DocumentPreview 组件使用文档 | ||
| 2 | + | ||
| 3 | +## 📖 概述 | ||
| 4 | + | ||
| 5 | +`DocumentPreview` 是一个统一的文档预览组件,支持 **PDF、Word、Excel、PPT** 等多种格式。 | ||
| 6 | + | ||
| 7 | +### 核心特性 | ||
| 8 | + | ||
| 9 | +✅ **多环境支持**:H5 + 微信小程序 | ||
| 10 | +✅ **智能预览**:根据文件大小自动选择最佳预览方式 | ||
| 11 | +✅ **全格式支持**:PDF、Word、Excel、PPT | ||
| 12 | +✅ **优雅降级**:大文件自动使用在线预览 | ||
| 13 | + | ||
| 14 | +--- | ||
| 15 | + | ||
| 16 | +## 🚀 快速开始 | ||
| 17 | + | ||
| 18 | +### 1. 基本使用 | ||
| 19 | + | ||
| 20 | +```vue | ||
| 21 | +<template> | ||
| 22 | + <view class="page"> | ||
| 23 | + <DocumentPreview | ||
| 24 | + :src="documentUrl" | ||
| 25 | + :fileType="fileType" | ||
| 26 | + :fileName="fileName" | ||
| 27 | + @rendered="handleRendered" | ||
| 28 | + @error="handleError" | ||
| 29 | + /> | ||
| 30 | + </view> | ||
| 31 | +</template> | ||
| 32 | + | ||
| 33 | +<script setup> | ||
| 34 | +import { ref } from 'vue' | ||
| 35 | +import DocumentPreview from '@/components/DocumentPreview/index.vue' | ||
| 36 | + | ||
| 37 | +const documentUrl = ref('https://example.com/document.pdf') | ||
| 38 | +const fileType = ref('pdf') // pdf, doc, docx, xls, xlsx, ppt, pptx | ||
| 39 | +const fileName = ref('重要文档.pdf') | ||
| 40 | + | ||
| 41 | +const handleRendered = () => { | ||
| 42 | + console.log('文档渲染完成') | ||
| 43 | +} | ||
| 44 | + | ||
| 45 | +const handleError = (err) => { | ||
| 46 | + console.error('文档渲染失败:', err) | ||
| 47 | +} | ||
| 48 | +</script> | ||
| 49 | +``` | ||
| 50 | + | ||
| 51 | +--- | ||
| 52 | + | ||
| 53 | +## 📋 Props | ||
| 54 | + | ||
| 55 | +| 属性 | 类型 | 默认值 | 必填 | 说明 | | ||
| 56 | +|------|------|--------|------|------| | ||
| 57 | +| `src` | `String` | - | ✅ | 文档 URL(必须 HTTPS) | | ||
| 58 | +| `fileType` | `String` | `''` | ❌ | 文档类型(不填则自动检测) | | ||
| 59 | +| `fileName` | `String` | `''` | ❌ | 文件名(用于显示) | | ||
| 60 | +| `show` | `Boolean` | `false` | ❌ | 是否显示(H5 PDF 预览用) | | ||
| 61 | + | ||
| 62 | +### fileType 支持的值 | ||
| 63 | + | ||
| 64 | +- `'pdf'` - PDF 文档 | ||
| 65 | +- `'doc'` / `'docx'` - Word 文档 | ||
| 66 | +- `'xls'` / `'xlsx'` - Excel 表格 | ||
| 67 | +- `'ppt'` / `'pptx'` - PowerPoint 演示文稿 | ||
| 68 | + | ||
| 69 | +--- | ||
| 70 | + | ||
| 71 | +## 🎯 Events | ||
| 72 | + | ||
| 73 | +| 事件名 | 参数 | 说明 | | ||
| 74 | +|--------|------|------| | ||
| 75 | +| `rendered` | - | 文档渲染完成(H5) | | ||
| 76 | +| `error` | `Error` | 文档渲染失败 | | ||
| 77 | +| `update:show` | `Boolean` | 更新显示状态(H5 PDF) | | ||
| 78 | + | ||
| 79 | +--- | ||
| 80 | + | ||
| 81 | +## 🔍 预览策略 | ||
| 82 | + | ||
| 83 | +### 小程序环境 | ||
| 84 | + | ||
| 85 | +| 文件大小 | 预览方式 | 体验 | | ||
| 86 | +|---------|---------|------| | ||
| 87 | +| **< 10MB** | 微信原生 API (`wx.openDocument`) | ⭐⭐⭐ 跳转新页面 | | ||
| 88 | +| **≥ 10MB** | web-view + 腾讯文档预览 | ⭐⭐⭐⭐⭐ 在线预览 | | ||
| 89 | + | ||
| 90 | +### H5 环境 | ||
| 91 | + | ||
| 92 | +- **PDF**:使用 `PdfPreview` 组件(内嵌预览) | ||
| 93 | +- **Office 文档**:使用 `OfficeViewer` 组件(内嵌预览) | ||
| 94 | + | ||
| 95 | +--- | ||
| 96 | + | ||
| 97 | +## 💡 使用场景 | ||
| 98 | + | ||
| 99 | +### 场景 1:预览小文件 PDF | ||
| 100 | + | ||
| 101 | +```vue | ||
| 102 | +<DocumentPreview | ||
| 103 | + src="https://example.com/small.pdf" | ||
| 104 | + fileType="pdf" | ||
| 105 | + fileName="产品手册.pdf" | ||
| 106 | +/> | ||
| 107 | +``` | ||
| 108 | + | ||
| 109 | +**小程序**:使用微信原生 API(快速)✨ | ||
| 110 | +**H5**:内嵌预览(优雅)✨ | ||
| 111 | + | ||
| 112 | +--- | ||
| 113 | + | ||
| 114 | +### 场景 2:预览大文件 Word | ||
| 115 | + | ||
| 116 | +```vue | ||
| 117 | +<DocumentPreview | ||
| 118 | + src="https://example.com/large-document.docx" | ||
| 119 | + fileType="docx" | ||
| 120 | + fileName="大型技术文档.docx" | ||
| 121 | +/> | ||
| 122 | +``` | ||
| 123 | + | ||
| 124 | +**小程序**:自动跳转到腾讯文档在线预览(支持大文件)🚀 | ||
| 125 | +**H5**:内嵌预览 | ||
| 126 | + | ||
| 127 | +--- | ||
| 128 | + | ||
| 129 | +### 场景 3:自动检测文件类型 | ||
| 130 | + | ||
| 131 | +```vue | ||
| 132 | +<DocumentPreview | ||
| 133 | + src="https://example.com/document.xlsx" | ||
| 134 | + fileName="销售数据表" | ||
| 135 | +/> | ||
| 136 | +``` | ||
| 137 | + | ||
| 138 | +不指定 `fileType`,组件会自动从 URL 中提取文件类型。 | ||
| 139 | + | ||
| 140 | +--- | ||
| 141 | + | ||
| 142 | +## ⚙️ 高级配置 | ||
| 143 | + | ||
| 144 | +### 域名白名单配置 | ||
| 145 | + | ||
| 146 | +**小程序环境必须配置业务域名白名单**: | ||
| 147 | + | ||
| 148 | +1. 登录[微信公众平台](https://mp.weixin.qq.com/) | ||
| 149 | +2. 进入「开发」→「开发管理」→「开发设置」 | ||
| 150 | +3. 找到「业务域名」 | ||
| 151 | +4. 添加以下域名: | ||
| 152 | + - `view.officeapps.live.com`(腾讯文档预览) | ||
| 153 | + - 您自己的文档服务器域名(如 `cdn.example.com`) | ||
| 154 | + | ||
| 155 | +### HTTPS 要求 | ||
| 156 | + | ||
| 157 | +**文档 URL 必须使用 HTTPS 协议**: | ||
| 158 | + | ||
| 159 | +```javascript | ||
| 160 | +// ✅ 正确 | ||
| 161 | +const url = 'https://cdn.example.com/document.pdf' | ||
| 162 | + | ||
| 163 | +// ❌ 错误(HTTP 不支持) | ||
| 164 | +const url = 'http://cdn.example.com/document.pdf' | ||
| 165 | +``` | ||
| 166 | + | ||
| 167 | +--- | ||
| 168 | + | ||
| 169 | +## 🐛 故障排查 | ||
| 170 | + | ||
| 171 | +### 问题 1:小程序提示"无法打开文档" | ||
| 172 | + | ||
| 173 | +**原因**:域名未配置白名单 | ||
| 174 | + | ||
| 175 | +**解决**: | ||
| 176 | +1. 在微信公众平台配置业务域名 | ||
| 177 | +2. 确保文档 URL 使用 HTTPS | ||
| 178 | +3. 清除小程序缓存重新打开 | ||
| 179 | + | ||
| 180 | +--- | ||
| 181 | + | ||
| 182 | +### 问题 2:大文件预览失败 | ||
| 183 | + | ||
| 184 | +**原因**:文件超过 10MB,但未正确跳转到 web-view | ||
| 185 | + | ||
| 186 | +**解决**: | ||
| 187 | +1. 检查 `pages/document-preview/index` 是否在 `app.config.js` 中注册 | ||
| 188 | +2. 检查 web-view 域名是否在白名单中 | ||
| 189 | +3. 查看控制台错误日志 | ||
| 190 | + | ||
| 191 | +--- | ||
| 192 | + | ||
| 193 | +### 问题 3:文件类型检测失败 | ||
| 194 | + | ||
| 195 | +**原因**:URL 中没有文件扩展名 | ||
| 196 | + | ||
| 197 | +**解决**:手动指定 `fileType` 属性 | ||
| 198 | + | ||
| 199 | +```vue | ||
| 200 | +<DocumentPreview | ||
| 201 | + src="https://api.example.com/download?id=123" | ||
| 202 | + fileType="pdf" | ||
| 203 | +/> | ||
| 204 | +``` | ||
| 205 | + | ||
| 206 | +--- | ||
| 207 | + | ||
| 208 | +## 📦 依赖 | ||
| 209 | + | ||
| 210 | +### 小程序环境 | ||
| 211 | + | ||
| 212 | +- Taro 4.x(内置) | ||
| 213 | +- web-view 组件(内置) | ||
| 214 | +- 无需额外依赖 | ||
| 215 | + | ||
| 216 | +### H5 环境 | ||
| 217 | + | ||
| 218 | +- 使用 iframe 预览(PDF 直开、Office 走腾讯文档预览) | ||
| 219 | +- 无需额外依赖 | ||
| 220 | + | ||
| 221 | +--- | ||
| 222 | + | ||
| 223 | +## 🎨 样式自定义 | ||
| 224 | + | ||
| 225 | +组件使用了 Scoped 样式,如需自定义,可以使用深度选择器: | ||
| 226 | + | ||
| 227 | +```vue | ||
| 228 | +<style lang="less" scoped> | ||
| 229 | +// 自定义加载容器 | ||
| 230 | +:deep(.loading-container) { | ||
| 231 | + background: #f0f0f0; | ||
| 232 | +} | ||
| 233 | + | ||
| 234 | +// 自定义错误提示 | ||
| 235 | +:deep(.error-container) { | ||
| 236 | + background: #fff0f0; | ||
| 237 | +} | ||
| 238 | +</style> | ||
| 239 | +``` | ||
| 240 | + | ||
| 241 | +--- | ||
| 242 | + | ||
| 243 | +## 📝 更新日志 | ||
| 244 | + | ||
| 245 | +### v1.0.0 (2025-01-30) | ||
| 246 | + | ||
| 247 | +✨ 新功能: | ||
| 248 | +- 支持多种文档格式预览 | ||
| 249 | +- 智能选择预览方式 | ||
| 250 | +- H5 + 小程序环境适配 | ||
| 251 | + | ||
| 252 | +🐛 Bug 修复: | ||
| 253 | +- 修复 PdfPreview 组件 watch 未导入问题 | ||
| 254 | + | ||
| 255 | +--- | ||
| 256 | + | ||
| 257 | +## 🤝 贡献 | ||
| 258 | + | ||
| 259 | +欢迎提交 Issue 和 Pull Request! | ||
| 260 | + | ||
| 261 | +--- | ||
| 262 | + | ||
| 263 | +## 📄 许可 | ||
| 264 | + | ||
| 265 | +MIT |
src/components/DocumentPreview/index.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Description: 统一文档预览组件 | ||
| 3 | + * @Date: 2025-01-30 | ||
| 4 | + * @Features: | ||
| 5 | + * - H5 环境:使用 OfficeViewer + PdfPreview 组件 | ||
| 6 | + * - 小程序环境:根据文件大小自动选择预览方式 | ||
| 7 | + * - 小于 10MB:微信原生 API (wx.openDocument) | ||
| 8 | + * - 大于等于 10MB:web-view + 腾讯文档预览 | ||
| 9 | +--> | ||
| 10 | +<template> | ||
| 11 | + <view class="document-preview"> | ||
| 12 | + <!-- #ifdef H5 --> | ||
| 13 | + <!-- H5 环境:使用现有组件 --> | ||
| 14 | + <OfficeViewer | ||
| 15 | + v-if="isOfficeDocument && src" | ||
| 16 | + :src="src" | ||
| 17 | + :fileType="finalFileType" | ||
| 18 | + @rendered="handleRendered" | ||
| 19 | + @error="handleError" | ||
| 20 | + /> | ||
| 21 | + | ||
| 22 | + <PdfPreview | ||
| 23 | + v-if="isPdfDocument && src" | ||
| 24 | + :url="src" | ||
| 25 | + :show="show" | ||
| 26 | + @update:show="handleUpdateShow" | ||
| 27 | + @onLoad="handlePdfLoad" | ||
| 28 | + /> | ||
| 29 | + <!-- #endif --> | ||
| 30 | + | ||
| 31 | + <!-- #ifdef WEAPP --> | ||
| 32 | + <!-- 小程序环境:使用微信原生 API 或 web-view --> | ||
| 33 | + <view class="preview-container"> | ||
| 34 | + <!-- 加载状态 --> | ||
| 35 | + <view v-if="loading" class="loading-container"> | ||
| 36 | + <IconFont name="Loading" size="24" class="animate-spin text-blue-600" /> | ||
| 37 | + <text class="loading-text">{{ loadingText }}</text> | ||
| 38 | + </view> | ||
| 39 | + | ||
| 40 | + <!-- 错误状态 --> | ||
| 41 | + <view v-else-if="error" class="error-container"> | ||
| 42 | + <IconFont name="Issue" size="48" color="#ff6b6b" /> | ||
| 43 | + <text class="error-text">{{ error }}</text> | ||
| 44 | + <nut-button type="primary" size="small" @click="retry"> | ||
| 45 | + 重试 | ||
| 46 | + </nut-button> | ||
| 47 | + </view> | ||
| 48 | + | ||
| 49 | + <!-- 预览按钮 --> | ||
| 50 | + <view v-else class="action-container"> | ||
| 51 | + <view class="file-info"> | ||
| 52 | + <IconFont :name="fileIcon" size="64" class="text-blue-600" /> | ||
| 53 | + <text class="file-name">{{ fileName || '未知文件' }}</text> | ||
| 54 | + <text class="file-size">{{ formatFileSize(fileSize) }}</text> | ||
| 55 | + </view> | ||
| 56 | + | ||
| 57 | + <nut-button | ||
| 58 | + type="primary" | ||
| 59 | + block | ||
| 60 | + @click="openDocument" | ||
| 61 | + :loading="loading" | ||
| 62 | + > | ||
| 63 | + {{ previewButtonText }} | ||
| 64 | + </nut-button> | ||
| 65 | + | ||
| 66 | + <text v-if="needWebView" class="hint-text"> | ||
| 67 | + 大文件将使用在线预览 | ||
| 68 | + </text> | ||
| 69 | + </view> | ||
| 70 | + </view> | ||
| 71 | + <!-- #endif --> | ||
| 72 | + </view> | ||
| 73 | +</template> | ||
| 74 | + | ||
| 75 | +<script setup> | ||
| 76 | +import { ref, computed, watch } from 'vue' | ||
| 77 | +import { getFileSize, detectFileType, formatFileSize } from './utils' | ||
| 78 | +import IconFont from '@/components/IconFont.vue' | ||
| 79 | + | ||
| 80 | +// #ifdef H5 | ||
| 81 | +import OfficeViewer from '../OfficeViewer.vue' | ||
| 82 | +import PdfPreview from '../PdfPreview.vue' | ||
| 83 | +// #endif | ||
| 84 | + | ||
| 85 | +// #ifdef WEAPP | ||
| 86 | +import Taro from '@tarojs/taro' | ||
| 87 | +// #endif | ||
| 88 | + | ||
| 89 | +// Props 定义 | ||
| 90 | +const props = defineProps({ | ||
| 91 | + // 文档 URL | ||
| 92 | + src: { | ||
| 93 | + type: String, | ||
| 94 | + required: true | ||
| 95 | + }, | ||
| 96 | + // 文件类型(可选,自动检测) | ||
| 97 | + fileType: { | ||
| 98 | + type: String, | ||
| 99 | + default: '' | ||
| 100 | + }, | ||
| 101 | + // 是否显示(H5 PDF 预览用) | ||
| 102 | + show: { | ||
| 103 | + type: Boolean, | ||
| 104 | + default: false | ||
| 105 | + }, | ||
| 106 | + // 文件名(用于显示) | ||
| 107 | + fileName: { | ||
| 108 | + type: String, | ||
| 109 | + default: '' | ||
| 110 | + } | ||
| 111 | +}) | ||
| 112 | + | ||
| 113 | +// Emits 定义 | ||
| 114 | +const emit = defineEmits(['rendered', 'error', 'update:show']) | ||
| 115 | + | ||
| 116 | +const finalFileType = computed(() => { | ||
| 117 | + const detectedType = detectFileType(props.src) | ||
| 118 | + return (props.fileType || detectedType || '').toLowerCase() | ||
| 119 | +}) | ||
| 120 | + | ||
| 121 | +const isOfficeDocument = computed(() => { | ||
| 122 | + return ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(finalFileType.value) | ||
| 123 | +}) | ||
| 124 | + | ||
| 125 | +const isPdfDocument = computed(() => { | ||
| 126 | + return finalFileType.value === 'pdf' | ||
| 127 | +}) | ||
| 128 | + | ||
| 129 | +// #ifdef WEAPP | ||
| 130 | +// 响应式数据 | ||
| 131 | +const loading = ref(false) | ||
| 132 | +const loadingText = ref('准备中...') | ||
| 133 | +const error = ref('') | ||
| 134 | +const fileSize = ref(0) | ||
| 135 | + | ||
| 136 | +// 计算属性 | ||
| 137 | +const needWebView = computed(() => fileSize.value === 0 || fileSize.value >= 10 * 1024 * 1024) | ||
| 138 | + | ||
| 139 | +const fileIcon = computed(() => { | ||
| 140 | + const type = finalFileType.value.toLowerCase() | ||
| 141 | + const iconMap = { | ||
| 142 | + pdf: 'Order', | ||
| 143 | + doc: 'Edit', | ||
| 144 | + docx: 'Edit', | ||
| 145 | + xls: 'Category', | ||
| 146 | + xlsx: 'Category', | ||
| 147 | + ppt: 'PlayCircleFill', | ||
| 148 | + pptx: 'PlayCircleFill' | ||
| 149 | + } | ||
| 150 | + return iconMap[type] || 'Link' | ||
| 151 | +}) | ||
| 152 | + | ||
| 153 | +const previewButtonText = computed(() => { | ||
| 154 | + return needWebView.value ? '在线预览文档' : '打开文档' | ||
| 155 | +}) | ||
| 156 | + | ||
| 157 | +/** | ||
| 158 | + * 初始化文件信息 | ||
| 159 | + */ | ||
| 160 | +const initFileInfo = async () => { | ||
| 161 | + if (!props.src) return | ||
| 162 | + | ||
| 163 | + loading.value = true | ||
| 164 | + loadingText.value = '检测文件...' | ||
| 165 | + error.value = '' | ||
| 166 | + | ||
| 167 | + try { | ||
| 168 | + // 获取文件大小 | ||
| 169 | + loadingText.value = '获取文件信息...' | ||
| 170 | + const size = await getFileSize(props.src) | ||
| 171 | + fileSize.value = size | ||
| 172 | + | ||
| 173 | + console.log('文件信息:', { | ||
| 174 | + url: props.src, | ||
| 175 | + type: finalFileType.value, | ||
| 176 | + size: size, | ||
| 177 | + needWebView: needWebView.value | ||
| 178 | + }) | ||
| 179 | + } catch (err) { | ||
| 180 | + console.error('获取文件信息失败:', err) | ||
| 181 | + error.value = '无法获取文件信息,请检查网络连接' | ||
| 182 | + } finally { | ||
| 183 | + loading.value = false | ||
| 184 | + } | ||
| 185 | +} | ||
| 186 | + | ||
| 187 | +/** | ||
| 188 | + * 打开文档 | ||
| 189 | + */ | ||
| 190 | +const openDocument = async () => { | ||
| 191 | + loading.value = true | ||
| 192 | + loadingText.value = needWebView.value ? '跳转到在线预览...' : '下载中...' | ||
| 193 | + error.value = '' | ||
| 194 | + | ||
| 195 | + try { | ||
| 196 | + if (needWebView.value) { | ||
| 197 | + // 大文件:使用 web-view 在线预览 | ||
| 198 | + await openWithWebView() | ||
| 199 | + } else { | ||
| 200 | + // 小文件:使用微信原生 API | ||
| 201 | + await openWithNativeAPI() | ||
| 202 | + } | ||
| 203 | + } catch (err) { | ||
| 204 | + console.error('打开文档失败:', err) | ||
| 205 | + error.value = err.message || '文档打开失败,请重试' | ||
| 206 | + } finally { | ||
| 207 | + loading.value = false | ||
| 208 | + } | ||
| 209 | +} | ||
| 210 | + | ||
| 211 | +/** | ||
| 212 | + * 使用微信原生 API 打开文档 | ||
| 213 | + */ | ||
| 214 | +const openWithNativeAPI = async () => { | ||
| 215 | + try { | ||
| 216 | + // 下载文件 | ||
| 217 | + const downloadRes = await Taro.downloadFile({ | ||
| 218 | + url: props.src, | ||
| 219 | + timeout: 30000 // 30秒超时 | ||
| 220 | + }) | ||
| 221 | + | ||
| 222 | + if (downloadRes.statusCode !== 200) { | ||
| 223 | + throw new Error('文件下载失败') | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | + // 打开文档 | ||
| 227 | + await Taro.openDocument({ | ||
| 228 | + filePath: downloadRes.tempFilePath, | ||
| 229 | + fileType: finalFileType.value | ||
| 230 | + }) | ||
| 231 | + | ||
| 232 | + console.log('文档打开成功') | ||
| 233 | + } catch (err) { | ||
| 234 | + console.error('微信原生 API 打开文档失败:', err) | ||
| 235 | + throw new Error('文档打开失败: ' + (err.errMsg || err.message)) | ||
| 236 | + } | ||
| 237 | +} | ||
| 238 | + | ||
| 239 | +/** | ||
| 240 | + * 使用 web-view 在线预览 | ||
| 241 | + */ | ||
| 242 | +const openWithWebView = async () => { | ||
| 243 | + try { | ||
| 244 | + // 跳转到 web-view 容器页面 | ||
| 245 | + const previewUrl = encodeURIComponent(props.src) | ||
| 246 | + const fileType = encodeURIComponent(finalFileType.value) | ||
| 247 | + | ||
| 248 | + await Taro.navigateTo({ | ||
| 249 | + url: `/pages/document-preview/index?url=${previewUrl}&type=${fileType}` | ||
| 250 | + }) | ||
| 251 | + | ||
| 252 | + console.log('跳转到在线预览页面') | ||
| 253 | + } catch (err) { | ||
| 254 | + console.error('跳转失败:', err) | ||
| 255 | + throw new Error('打开预览页面失败') | ||
| 256 | + } | ||
| 257 | +} | ||
| 258 | + | ||
| 259 | +/** | ||
| 260 | + * 重试 | ||
| 261 | + */ | ||
| 262 | +const retry = () => { | ||
| 263 | + initFileInfo() | ||
| 264 | +} | ||
| 265 | + | ||
| 266 | +// 监听 src 变化 | ||
| 267 | +watch(() => props.src, (newSrc) => { | ||
| 268 | + if (newSrc) { | ||
| 269 | + initFileInfo() | ||
| 270 | + } | ||
| 271 | +}, { immediate: true }) | ||
| 272 | +// #endif | ||
| 273 | + | ||
| 274 | +// #ifdef H5 | ||
| 275 | +const handleRendered = () => { | ||
| 276 | + console.log('H5: 文档渲染完成') | ||
| 277 | + emit('rendered') | ||
| 278 | +} | ||
| 279 | + | ||
| 280 | +const handleError = (err) => { | ||
| 281 | + console.error('H5: 文档渲染失败', err) | ||
| 282 | + emit('error', err) | ||
| 283 | +} | ||
| 284 | + | ||
| 285 | +const handleUpdateShow = (value) => { | ||
| 286 | + emit('update:show', value) | ||
| 287 | +} | ||
| 288 | + | ||
| 289 | +const handlePdfLoad = () => { | ||
| 290 | + console.log('H5: PDF 加载完成') | ||
| 291 | +} | ||
| 292 | +// #endif | ||
| 293 | +</script> | ||
| 294 | + | ||
| 295 | +<style lang="less" scoped> | ||
| 296 | +.document-preview { | ||
| 297 | + width: 100%; | ||
| 298 | + height: 100%; | ||
| 299 | + background: #f5f5f5; | ||
| 300 | +} | ||
| 301 | + | ||
| 302 | +// #ifdef WEAPP | ||
| 303 | +.preview-container { | ||
| 304 | + display: flex; | ||
| 305 | + flex-direction: column; | ||
| 306 | + align-items: center; | ||
| 307 | + justify-content: center; | ||
| 308 | + min-height: 800rpx; | ||
| 309 | + padding: 60rpx; | ||
| 310 | + background: #fff; | ||
| 311 | + border-radius: 32rpx; | ||
| 312 | +} | ||
| 313 | + | ||
| 314 | +.loading-container { | ||
| 315 | + display: flex; | ||
| 316 | + flex-direction: column; | ||
| 317 | + align-items: center; | ||
| 318 | + gap: 40rpx; | ||
| 319 | + | ||
| 320 | + .loading-text { | ||
| 321 | + font-size: 56rpx; | ||
| 322 | + color: #999; | ||
| 323 | + } | ||
| 324 | +} | ||
| 325 | + | ||
| 326 | +.error-container { | ||
| 327 | + display: flex; | ||
| 328 | + flex-direction: column; | ||
| 329 | + align-items: center; | ||
| 330 | + gap: 40rpx; | ||
| 331 | + padding: 80rpx 40rpx; | ||
| 332 | + | ||
| 333 | + .error-text { | ||
| 334 | + font-size: 56rpx; | ||
| 335 | + color: #666; | ||
| 336 | + text-align: center; | ||
| 337 | + line-height: 1.6; | ||
| 338 | + } | ||
| 339 | +} | ||
| 340 | + | ||
| 341 | +.action-container { | ||
| 342 | + display: flex; | ||
| 343 | + flex-direction: column; | ||
| 344 | + align-items: center; | ||
| 345 | + gap: 60rpx; | ||
| 346 | + width: 100%; | ||
| 347 | + max-width: 1000rpx; | ||
| 348 | + | ||
| 349 | + .file-info { | ||
| 350 | + display: flex; | ||
| 351 | + flex-direction: column; | ||
| 352 | + align-items: center; | ||
| 353 | + gap: 40rpx; | ||
| 354 | + padding: 80rpx; | ||
| 355 | + background: #f8f9fa; | ||
| 356 | + border-radius: 32rpx; | ||
| 357 | + width: 100%; | ||
| 358 | + | ||
| 359 | + .file-name { | ||
| 360 | + font-size: 64rpx; | ||
| 361 | + font-weight: 500; | ||
| 362 | + color: #333; | ||
| 363 | + text-align: center; | ||
| 364 | + word-break: break-all; | ||
| 365 | + } | ||
| 366 | + | ||
| 367 | + .file-size { | ||
| 368 | + font-size: 48rpx; | ||
| 369 | + color: #999; | ||
| 370 | + } | ||
| 371 | + } | ||
| 372 | + | ||
| 373 | + .hint-text { | ||
| 374 | + font-size: 48rpx; | ||
| 375 | + color: #ff9800; | ||
| 376 | + text-align: center; | ||
| 377 | + } | ||
| 378 | +} | ||
| 379 | +// #endif | ||
| 380 | +</style> |
src/components/DocumentPreview/utils.js
0 → 100644
| 1 | +/** | ||
| 2 | + * @Description: 文档预览工具函数 | ||
| 3 | + * @Date: 2025-01-30 | ||
| 4 | + */ | ||
| 5 | + | ||
| 6 | +// #ifdef WEAPP | ||
| 7 | +import Taro from '@tarojs/taro' | ||
| 8 | +// #endif | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 从 URL 中检测文件类型 | ||
| 12 | + * @param {string} url - 文档 URL | ||
| 13 | + * @returns {string} 文件类型(小写) | ||
| 14 | + */ | ||
| 15 | +export function detectFileType(url) { | ||
| 16 | + if (!url) return '' | ||
| 17 | + | ||
| 18 | + // 从 URL 中提取扩展名 | ||
| 19 | + const match = url.match(/\.([a-z0-9]+)(?:\?|#|$)/i) | ||
| 20 | + | ||
| 21 | + if (match && match[1]) { | ||
| 22 | + const ext = match[1].toLowerCase() | ||
| 23 | + | ||
| 24 | + // 映射常见扩展名到统一类型 | ||
| 25 | + const typeMap = { | ||
| 26 | + pdf: 'pdf', | ||
| 27 | + doc: 'doc', | ||
| 28 | + docx: 'docx', | ||
| 29 | + xls: 'xls', | ||
| 30 | + xlsx: 'xlsx', | ||
| 31 | + ppt: 'ppt', | ||
| 32 | + pptx: 'pptx' | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + return typeMap[ext] || ext | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + // 如果无法从 URL 判断,尝试从 Content-Type 头(需要后端支持) | ||
| 39 | + return '' | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +/** | ||
| 43 | + * 获取文件大小(通过 HEAD 请求) | ||
| 44 | + * @param {string} url - 文档 URL | ||
| 45 | + * @returns {Promise<number>} 文件大小(字节) | ||
| 46 | + */ | ||
| 47 | +export async function getFileSize(url) { | ||
| 48 | + return new Promise((resolve) => { | ||
| 49 | + // #ifdef H5 | ||
| 50 | + // H5 环境:使用 fetch HEAD 请求 | ||
| 51 | + fetch(url, { method: 'HEAD' }) | ||
| 52 | + .then(response => { | ||
| 53 | + const contentLength = response.headers.get('Content-Length') | ||
| 54 | + if (contentLength) { | ||
| 55 | + resolve(parseInt(contentLength, 10)) | ||
| 56 | + } else { | ||
| 57 | + // 无法获取大小,返回 0(将使用 web-view) | ||
| 58 | + resolve(0) | ||
| 59 | + } | ||
| 60 | + }) | ||
| 61 | + .catch(err => { | ||
| 62 | + console.error('获取文件大小失败:', err) | ||
| 63 | + // 失败时返回 0,将使用 web-view | ||
| 64 | + resolve(0) | ||
| 65 | + }) | ||
| 66 | + // #endif | ||
| 67 | + | ||
| 68 | + // #ifdef WEAPP | ||
| 69 | + // 小程序环境:使用 Taro.request HEAD 请求 | ||
| 70 | + Taro.request({ | ||
| 71 | + url: url, | ||
| 72 | + method: 'HEAD', | ||
| 73 | + success: (res) => { | ||
| 74 | + const contentLength = res.header['Content-Length'] || res.header['content-length'] | ||
| 75 | + if (contentLength) { | ||
| 76 | + resolve(parseInt(contentLength, 10)) | ||
| 77 | + } else { | ||
| 78 | + // 无法获取大小,返回 0(将使用 web-view) | ||
| 79 | + resolve(0) | ||
| 80 | + } | ||
| 81 | + }, | ||
| 82 | + fail: (err) => { | ||
| 83 | + console.error('获取文件大小失败:', err) | ||
| 84 | + // 失败时返回 0,将使用 web-view | ||
| 85 | + resolve(0) | ||
| 86 | + } | ||
| 87 | + }) | ||
| 88 | + // #endif | ||
| 89 | + }) | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | +/** | ||
| 93 | + * 格式化文件大小显示 | ||
| 94 | + * @param {number} bytes - 文件大小(字节) | ||
| 95 | + * @returns {string} 格式化后的字符串 | ||
| 96 | + */ | ||
| 97 | +export function formatFileSize(bytes) { | ||
| 98 | + if (!bytes || bytes === 0) return '未知大小' | ||
| 99 | + | ||
| 100 | + const units = ['B', 'KB', 'MB', 'GB'] | ||
| 101 | + let size = bytes | ||
| 102 | + let unitIndex = 0 | ||
| 103 | + | ||
| 104 | + while (size >= 1024 && unitIndex < units.length - 1) { | ||
| 105 | + size /= 1024 | ||
| 106 | + unitIndex++ | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + // 保留两位小数 | ||
| 110 | + const formatted = size.toFixed(2).replace(/\.00$/, '') | ||
| 111 | + | ||
| 112 | + return `${formatted} ${units[unitIndex]}` | ||
| 113 | +} | ||
| 114 | + | ||
| 115 | +/** | ||
| 116 | + * 判断是否为支持的文档类型 | ||
| 117 | + * @param {string} fileType - 文件类型 | ||
| 118 | + * @returns {boolean} | ||
| 119 | + */ | ||
| 120 | +export function isSupportedDocumentType(fileType) { | ||
| 121 | + const supportedTypes = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'] | ||
| 122 | + return supportedTypes.includes(fileType?.toLowerCase()) | ||
| 123 | +} | ||
| 124 | + | ||
| 125 | +/** | ||
| 126 | + * 获取文件图标名称 | ||
| 127 | + * @param {string} fileType - 文件类型 | ||
| 128 | + * @returns {string} 图标名称 | ||
| 129 | + */ | ||
| 130 | +export function getFileIconName(fileType) { | ||
| 131 | + const iconMap = { | ||
| 132 | + pdf: 'pdf', | ||
| 133 | + doc: 'word', | ||
| 134 | + docx: 'word', | ||
| 135 | + xls: 'excel', | ||
| 136 | + xlsx: 'excel', | ||
| 137 | + ppt: 'ppt', | ||
| 138 | + pptx: 'ppt' | ||
| 139 | + } | ||
| 140 | + return iconMap[fileType?.toLowerCase()] || 'file' | ||
| 141 | +} | ||
| 142 | + | ||
| 143 | +/** | ||
| 144 | + * 生成腾讯文档预览 URL | ||
| 145 | + * @param {string} url - 原始文档 URL | ||
| 146 | + * @returns {string} 腾讯文档预览 URL | ||
| 147 | + */ | ||
| 148 | +export function getTencentPreviewUrl(url) { | ||
| 149 | + const encodedUrl = encodeURIComponent(url) | ||
| 150 | + return `https://view.officeapps.live.com/op/view.aspx?src=${encodedUrl}` | ||
| 151 | +} | ||
| 152 | + | ||
| 153 | +/** | ||
| 154 | + * 生成微软在线预览 URL | ||
| 155 | + * @param {string} url - 原始文档 URL | ||
| 156 | + * @returns {string} 微软预览 URL | ||
| 157 | + */ | ||
| 158 | +export function getMicrosoftPreviewUrl(url) { | ||
| 159 | + const encodedUrl = encodeURIComponent(url) | ||
| 160 | + return `https://view.officeapps.live.com/op/embed.aspx?src=${encodedUrl}` | ||
| 161 | +} |
| ... | @@ -17,12 +17,19 @@ import { | ... | @@ -17,12 +17,19 @@ import { |
| 17 | Check, | 17 | Check, |
| 18 | Checklist, | 18 | Checklist, |
| 19 | Clock, | 19 | Clock, |
| 20 | + Download, | ||
| 20 | Edit, | 21 | Edit, |
| 21 | Find, | 22 | Find, |
| 22 | Home, | 23 | Home, |
| 24 | + Issue, | ||
| 25 | + Link, | ||
| 26 | + Loading, | ||
| 27 | + Location, | ||
| 23 | My, | 28 | My, |
| 24 | Order, | 29 | Order, |
| 30 | + People, | ||
| 25 | PlayCircleFill, | 31 | PlayCircleFill, |
| 32 | + Refresh, | ||
| 26 | RectRight, | 33 | RectRight, |
| 27 | RectLeft, | 34 | RectLeft, |
| 28 | Search, | 35 | Search, |
| ... | @@ -58,12 +65,19 @@ const icons = { | ... | @@ -58,12 +65,19 @@ const icons = { |
| 58 | Check, | 65 | Check, |
| 59 | Checklist, | 66 | Checklist, |
| 60 | Clock, | 67 | Clock, |
| 68 | + Download, | ||
| 61 | Edit, | 69 | Edit, |
| 62 | Find, | 70 | Find, |
| 63 | Home, | 71 | Home, |
| 72 | + Issue, | ||
| 73 | + Link, | ||
| 74 | + Loading, | ||
| 75 | + Location, | ||
| 64 | My, | 76 | My, |
| 65 | Order, | 77 | Order, |
| 78 | + People, | ||
| 66 | PlayCircleFill, | 79 | PlayCircleFill, |
| 80 | + Refresh, | ||
| 67 | RectRight, | 81 | RectRight, |
| 68 | RectLeft, | 82 | RectLeft, |
| 69 | Search, | 83 | Search, | ... | ... |
src/components/OfficeViewer.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="office-viewer"> | ||
| 3 | + <view v-if="error" class="error-container"> | ||
| 4 | + <IconFont name="Issue" size="24" color="#ff6b6b" /> | ||
| 5 | + <text class="error-text">{{ error }}</text> | ||
| 6 | + <nut-button type="primary" size="small" class="retry-btn" @click="retry">重试</nut-button> | ||
| 7 | + </view> | ||
| 8 | + | ||
| 9 | + <view v-else class="document-container"> | ||
| 10 | + <!-- #ifdef H5 --> | ||
| 11 | + <iframe | ||
| 12 | + v-if="preview_url" | ||
| 13 | + :src="preview_url" | ||
| 14 | + frameborder="0" | ||
| 15 | + class="preview-iframe" | ||
| 16 | + :style="{ height: container_height }" | ||
| 17 | + @load="on_loaded" | ||
| 18 | + /> | ||
| 19 | + <view v-else class="unsupported-container"> | ||
| 20 | + <IconFont name="Issue" size="24" color="#ff6b6b" /> | ||
| 21 | + <text class="unsupported-text">不支持的文件类型: {{ normalized_type || '未知' }}</text> | ||
| 22 | + </view> | ||
| 23 | + <!-- #endif --> | ||
| 24 | + | ||
| 25 | + <!-- #ifdef WEAPP --> | ||
| 26 | + <view class="unsupported-container"> | ||
| 27 | + <IconFont name="Issue" size="24" color="#ff6b6b" /> | ||
| 28 | + <text class="unsupported-text">小程序不支持内嵌 Office 预览</text> | ||
| 29 | + </view> | ||
| 30 | + <!-- #endif --> | ||
| 31 | + </view> | ||
| 32 | + | ||
| 33 | + <view v-if="loading" class="loading-overlay"> | ||
| 34 | + <IconFont name="Loading" size="24" class="animate-spin text-blue-600" /> | ||
| 35 | + <text class="loading-text">加载中...</text> | ||
| 36 | + </view> | ||
| 37 | + </view> | ||
| 38 | +</template> | ||
| 39 | + | ||
| 40 | +<script setup> | ||
| 41 | +import { ref, computed, watch } from 'vue' | ||
| 42 | +import IconFont from '@/components/IconFont.vue' | ||
| 43 | +import { getTencentPreviewUrl } from '@/components/DocumentPreview/utils' | ||
| 44 | + | ||
| 45 | +const props = defineProps({ | ||
| 46 | + src: { | ||
| 47 | + type: [String, ArrayBuffer], | ||
| 48 | + required: true | ||
| 49 | + }, | ||
| 50 | + fileType: { | ||
| 51 | + type: String, | ||
| 52 | + default: '' | ||
| 53 | + }, | ||
| 54 | + height: { | ||
| 55 | + type: String, | ||
| 56 | + default: '70vh' | ||
| 57 | + } | ||
| 58 | +}) | ||
| 59 | + | ||
| 60 | +const emit = defineEmits(['rendered', 'error', 'retry']) | ||
| 61 | + | ||
| 62 | +const loading = ref(false) | ||
| 63 | +const error = ref('') | ||
| 64 | + | ||
| 65 | +const container_height = computed(() => props.height) | ||
| 66 | + | ||
| 67 | +const normalized_type = computed(() => { | ||
| 68 | + const raw_type = (props.fileType || '').toLowerCase() | ||
| 69 | + if (raw_type === 'doc' || raw_type === 'docx') return 'docx' | ||
| 70 | + if (raw_type === 'xls' || raw_type === 'xlsx') return 'xlsx' | ||
| 71 | + if (raw_type === 'ppt' || raw_type === 'pptx') return 'pptx' | ||
| 72 | + if (raw_type === 'pdf') return 'pdf' | ||
| 73 | + return raw_type | ||
| 74 | +}) | ||
| 75 | + | ||
| 76 | +const preview_url = computed(() => { | ||
| 77 | + if (!props.src || typeof props.src !== 'string') return '' | ||
| 78 | + | ||
| 79 | + if (normalized_type.value === 'pdf') return props.src | ||
| 80 | + if (['docx', 'xlsx', 'pptx'].includes(normalized_type.value)) { | ||
| 81 | + return getTencentPreviewUrl(props.src) | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + return '' | ||
| 85 | +}) | ||
| 86 | + | ||
| 87 | +const on_loaded = () => { | ||
| 88 | + loading.value = false | ||
| 89 | + emit('rendered') | ||
| 90 | +} | ||
| 91 | + | ||
| 92 | +const retry = () => { | ||
| 93 | + loading.value = true | ||
| 94 | + error.value = '' | ||
| 95 | + emit('retry') | ||
| 96 | +} | ||
| 97 | + | ||
| 98 | +watch(() => [props.src, props.fileType], () => { | ||
| 99 | + error.value = '' | ||
| 100 | + | ||
| 101 | + if (!props.src) { | ||
| 102 | + loading.value = false | ||
| 103 | + error.value = '文档地址不能为空' | ||
| 104 | + emit('error', new Error(error.value)) | ||
| 105 | + return | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + loading.value = true | ||
| 109 | + | ||
| 110 | + if (!preview_url.value) { | ||
| 111 | + loading.value = false | ||
| 112 | + error.value = '文档类型不支持或地址格式不正确' | ||
| 113 | + emit('error', new Error(error.value)) | ||
| 114 | + } | ||
| 115 | +}, { immediate: true }) | ||
| 116 | +</script> | ||
| 117 | + | ||
| 118 | +<style lang="less" scoped> | ||
| 119 | +.office-viewer { | ||
| 120 | + position: relative; | ||
| 121 | + width: 100%; | ||
| 122 | + height: 100%; | ||
| 123 | + background: #fff; | ||
| 124 | + border-radius: 8px; | ||
| 125 | + overflow: hidden; | ||
| 126 | + | ||
| 127 | + .error-container, | ||
| 128 | + .unsupported-container { | ||
| 129 | + display: flex; | ||
| 130 | + flex-direction: column; | ||
| 131 | + align-items: center; | ||
| 132 | + justify-content: center; | ||
| 133 | + height: 400rpx; | ||
| 134 | + gap: 24rpx; | ||
| 135 | + padding: 40rpx; | ||
| 136 | + | ||
| 137 | + .error-text, | ||
| 138 | + .unsupported-text { | ||
| 139 | + font-size: 28rpx; | ||
| 140 | + color: #666; | ||
| 141 | + text-align: center; | ||
| 142 | + line-height: 1.5; | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + .retry-btn { | ||
| 146 | + margin-top: 16rpx; | ||
| 147 | + } | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + .document-container { | ||
| 151 | + width: 100%; | ||
| 152 | + height: 100%; | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + .preview-iframe { | ||
| 156 | + width: 100%; | ||
| 157 | + border: none; | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + .loading-overlay { | ||
| 161 | + position: absolute; | ||
| 162 | + top: 50%; | ||
| 163 | + left: 50%; | ||
| 164 | + transform: translate(-50%, -50%); | ||
| 165 | + z-index: 10; | ||
| 166 | + background: rgba(255, 255, 255, 0.9); | ||
| 167 | + border-radius: 16rpx; | ||
| 168 | + padding: 24rpx 32rpx; | ||
| 169 | + display: flex; | ||
| 170 | + align-items: center; | ||
| 171 | + gap: 16rpx; | ||
| 172 | + | ||
| 173 | + .loading-text { | ||
| 174 | + font-size: 28rpx; | ||
| 175 | + color: #333; | ||
| 176 | + } | ||
| 177 | + } | ||
| 178 | +} | ||
| 179 | +</style> |
src/components/PdfPreview.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2024-01-17 | ||
| 3 | + * @Description: PDF预览组件 | ||
| 4 | +--> | ||
| 5 | +<template> | ||
| 6 | + <view v-if="show" class="pdf-preview"> | ||
| 7 | + <view class="mask" @tap="close"></view> | ||
| 8 | + <view class="panel"> | ||
| 9 | + <view class="header"> | ||
| 10 | + <text class="title">{{ title || 'PDF 预览' }}</text> | ||
| 11 | + <view class="close" @tap="close"> | ||
| 12 | + <IconFont name="Del" size="16" color="#666" /> | ||
| 13 | + </view> | ||
| 14 | + </view> | ||
| 15 | + | ||
| 16 | + <view class="body"> | ||
| 17 | + <!-- #ifdef H5 --> | ||
| 18 | + <iframe | ||
| 19 | + v-if="url" | ||
| 20 | + class="pdf-iframe" | ||
| 21 | + :src="url" | ||
| 22 | + frameborder="0" | ||
| 23 | + @load="on_loaded" | ||
| 24 | + /> | ||
| 25 | + <view v-else class="empty"> | ||
| 26 | + <text class="empty-text">PDF 地址不能为空</text> | ||
| 27 | + </view> | ||
| 28 | + <!-- #endif --> | ||
| 29 | + | ||
| 30 | + <!-- #ifdef WEAPP --> | ||
| 31 | + <view class="empty"> | ||
| 32 | + <text class="empty-text">小程序不支持内嵌 PDF 预览</text> | ||
| 33 | + </view> | ||
| 34 | + <!-- #endif --> | ||
| 35 | + </view> | ||
| 36 | + | ||
| 37 | + <view v-if="loading" class="loading"> | ||
| 38 | + <IconFont name="Loading" size="24" class="animate-spin text-blue-600" /> | ||
| 39 | + <text class="loading-text">加载中...</text> | ||
| 40 | + </view> | ||
| 41 | + </view> | ||
| 42 | + </view> | ||
| 43 | +</template> | ||
| 44 | + | ||
| 45 | +<script setup> | ||
| 46 | +import { ref, watch } from 'vue' | ||
| 47 | +import IconFont from '@/components/IconFont.vue' | ||
| 48 | + | ||
| 49 | +const props = defineProps({ | ||
| 50 | + show: { | ||
| 51 | + type: Boolean, | ||
| 52 | + default: false | ||
| 53 | + }, | ||
| 54 | + url: { | ||
| 55 | + type: String, | ||
| 56 | + default: '' | ||
| 57 | + }, | ||
| 58 | + title: { | ||
| 59 | + type: String, | ||
| 60 | + default: '' | ||
| 61 | + } | ||
| 62 | +}) | ||
| 63 | + | ||
| 64 | +const emit = defineEmits(['update:show', 'onLoad']) | ||
| 65 | + | ||
| 66 | +const loading = ref(false) | ||
| 67 | + | ||
| 68 | +const close = () => { | ||
| 69 | + emit('update:show', false) | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +const on_loaded = () => { | ||
| 73 | + loading.value = false | ||
| 74 | + emit('onLoad', false) | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +watch(() => props.show, (new_show) => { | ||
| 78 | + if (new_show) { | ||
| 79 | + loading.value = true | ||
| 80 | + } else { | ||
| 81 | + loading.value = false | ||
| 82 | + } | ||
| 83 | +}, { immediate: true }) | ||
| 84 | +</script> | ||
| 85 | + | ||
| 86 | +<style lang="less" scoped> | ||
| 87 | +.pdf-preview { | ||
| 88 | + position: fixed; | ||
| 89 | + inset: 0; | ||
| 90 | + z-index: 999; | ||
| 91 | + | ||
| 92 | + .mask { | ||
| 93 | + position: absolute; | ||
| 94 | + inset: 0; | ||
| 95 | + background: rgba(0, 0, 0, 0.5); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + .panel { | ||
| 99 | + position: absolute; | ||
| 100 | + top: 0; | ||
| 101 | + right: 0; | ||
| 102 | + width: 100%; | ||
| 103 | + height: 100%; | ||
| 104 | + background: #fff; | ||
| 105 | + display: flex; | ||
| 106 | + flex-direction: column; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + .header { | ||
| 110 | + height: 96rpx; | ||
| 111 | + padding: 0 24rpx; | ||
| 112 | + display: flex; | ||
| 113 | + align-items: center; | ||
| 114 | + justify-content: space-between; | ||
| 115 | + border-bottom: 2rpx solid #f1f5f9; | ||
| 116 | + | ||
| 117 | + .title { | ||
| 118 | + font-size: 30rpx; | ||
| 119 | + font-weight: 600; | ||
| 120 | + color: #111827; | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + .close { | ||
| 124 | + width: 64rpx; | ||
| 125 | + height: 64rpx; | ||
| 126 | + display: flex; | ||
| 127 | + align-items: center; | ||
| 128 | + justify-content: center; | ||
| 129 | + border-radius: 9999rpx; | ||
| 130 | + background: #f3f4f6; | ||
| 131 | + } | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + .body { | ||
| 135 | + flex: 1; | ||
| 136 | + position: relative; | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + .pdf-iframe { | ||
| 140 | + width: 100%; | ||
| 141 | + height: 100%; | ||
| 142 | + border: none; | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + .empty { | ||
| 146 | + width: 100%; | ||
| 147 | + height: 100%; | ||
| 148 | + display: flex; | ||
| 149 | + align-items: center; | ||
| 150 | + justify-content: center; | ||
| 151 | + background: #f9fafb; | ||
| 152 | + | ||
| 153 | + .empty-text { | ||
| 154 | + font-size: 28rpx; | ||
| 155 | + color: #6b7280; | ||
| 156 | + } | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | + .loading { | ||
| 160 | + position: absolute; | ||
| 161 | + top: 50%; | ||
| 162 | + left: 50%; | ||
| 163 | + transform: translate(-50%, -50%); | ||
| 164 | + background: rgba(255, 255, 255, 0.9); | ||
| 165 | + border-radius: 16rpx; | ||
| 166 | + padding: 24rpx 32rpx; | ||
| 167 | + display: flex; | ||
| 168 | + align-items: center; | ||
| 169 | + gap: 16rpx; | ||
| 170 | + | ||
| 171 | + .loading-text { | ||
| 172 | + font-size: 28rpx; | ||
| 173 | + color: #333; | ||
| 174 | + } | ||
| 175 | + } | ||
| 176 | +} | ||
| 177 | +</style> |
src/pages/document-demo/index.config.js
0 → 100644
src/pages/document-demo/index.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Description: 文档预览示例页面 | ||
| 3 | + * @Date: 2025-01-30 | ||
| 4 | + * @Usage: 展示 DocumentPreview 组件的各种使用场景 | ||
| 5 | +--> | ||
| 6 | +<template> | ||
| 7 | + <view class="document-demo-page"> | ||
| 8 | + <nut-tabs v-model="activeTab"> | ||
| 9 | + <nut-tab-pane title="PDF 预览" pane-key="pdf"> | ||
| 10 | + <DocumentPreview | ||
| 11 | + src="https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" | ||
| 12 | + fileType="pdf" | ||
| 13 | + fileName="示例 PDF 文档.pdf" | ||
| 14 | + @rendered="handleRendered" | ||
| 15 | + @error="handleError" | ||
| 16 | + /> | ||
| 17 | + </nut-tab-pane> | ||
| 18 | + | ||
| 19 | + <nut-tab-pane title="Word 预览" pane-key="word"> | ||
| 20 | + <DocumentPreview | ||
| 21 | + src="https://calibre-ebook.com/downloads/demos/demo.docx" | ||
| 22 | + fileType="docx" | ||
| 23 | + fileName="示例 Word 文档.docx" | ||
| 24 | + @rendered="handleRendered" | ||
| 25 | + @error="handleError" | ||
| 26 | + /> | ||
| 27 | + </nut-tab-pane> | ||
| 28 | + | ||
| 29 | + <nut-tab-pane title="Excel 预览" pane-key="excel"> | ||
| 30 | + <DocumentPreview | ||
| 31 | + src="https://filesamples.com/samples/document/xlsx/sample1.xlsx" | ||
| 32 | + fileType="xlsx" | ||
| 33 | + fileName="示例 Excel 文档.xlsx" | ||
| 34 | + @rendered="handleRendered" | ||
| 35 | + @error="handleError" | ||
| 36 | + /> | ||
| 37 | + </nut-tab-pane> | ||
| 38 | + | ||
| 39 | + <nut-tab-pane title="PPT 预览" pane-key="ppt"> | ||
| 40 | + <DocumentPreview | ||
| 41 | + src="https://filesamples.com/samples/document/ppt/sample1.ppt" | ||
| 42 | + fileType="ppt" | ||
| 43 | + fileName="示例 PPT 文档.ppt" | ||
| 44 | + @rendered="handleRendered" | ||
| 45 | + @error="handleError" | ||
| 46 | + /> | ||
| 47 | + </nut-tab-pane> | ||
| 48 | + </nut-tabs> | ||
| 49 | + </view> | ||
| 50 | +</template> | ||
| 51 | + | ||
| 52 | +<script setup> | ||
| 53 | +import { ref } from 'vue' | ||
| 54 | +import DocumentPreview from '@/components/DocumentPreview/index.vue' | ||
| 55 | + | ||
| 56 | +// #ifdef WEAPP | ||
| 57 | +import Taro from '@tarojs/taro' | ||
| 58 | +// #endif | ||
| 59 | + | ||
| 60 | +const activeTab = ref('pdf') | ||
| 61 | + | ||
| 62 | +const handleRendered = () => { | ||
| 63 | + console.log('文档渲染完成') | ||
| 64 | + // #ifdef WEAPP | ||
| 65 | + Taro.showToast({ | ||
| 66 | + title: '文档加载完成', | ||
| 67 | + icon: 'success' | ||
| 68 | + }) | ||
| 69 | + // #endif | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +const handleError = (err) => { | ||
| 73 | + console.error('文档渲染失败:', err) | ||
| 74 | + // #ifdef WEAPP | ||
| 75 | + Taro.showToast({ | ||
| 76 | + title: '文档加载失败', | ||
| 77 | + icon: 'error' | ||
| 78 | + }) | ||
| 79 | + // #endif | ||
| 80 | +} | ||
| 81 | +</script> | ||
| 82 | + | ||
| 83 | +<style lang="less" scoped> | ||
| 84 | +.document-demo-page { | ||
| 85 | + min-height: 100vh; | ||
| 86 | + background: #f5f5f5; | ||
| 87 | +} | ||
| 88 | + | ||
| 89 | +.placeholder { | ||
| 90 | + display: flex; | ||
| 91 | + flex-direction: column; | ||
| 92 | + align-items: center; | ||
| 93 | + justify-content: center; | ||
| 94 | + min-height: 800rpx; | ||
| 95 | + padding: 40rpx; | ||
| 96 | + gap: 20rpx; | ||
| 97 | + | ||
| 98 | + .placeholder-text { | ||
| 99 | + font-size: 32rpx; | ||
| 100 | + font-weight: 500; | ||
| 101 | + color: #333; | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + .placeholder-hint { | ||
| 105 | + font-size: 28rpx; | ||
| 106 | + color: #999; | ||
| 107 | + } | ||
| 108 | +} | ||
| 109 | +</style> |
src/pages/document-preview/index.config.js
0 → 100644
src/pages/document-preview/index.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Description: 文档在线预览页面(web-view 容器) | ||
| 3 | + * @Date: 2025-01-30 | ||
| 4 | + * @Usage: 用于大文件(>= 10MB)的在线预览 | ||
| 5 | +--> | ||
| 6 | +<template> | ||
| 7 | + <view class="document-preview-page"> | ||
| 8 | + <!-- #ifdef WEAPP --> | ||
| 9 | + <web-view :src="previewUrl" @message="handleMessage" @load="handleLoad" @error="handleError" /> | ||
| 10 | + <!-- #endif --> | ||
| 11 | + | ||
| 12 | + <!-- #ifdef H5 --> | ||
| 13 | + <iframe :src="previewUrl" frameborder="0" class="preview-iframe" /> | ||
| 14 | + <!-- #endif --> | ||
| 15 | + </view> | ||
| 16 | +</template> | ||
| 17 | + | ||
| 18 | +<script setup> | ||
| 19 | +import { computed, ref } from 'vue' | ||
| 20 | +import { useLoad, useReady } from '@tarojs/taro' | ||
| 21 | +import Taro from '@tarojs/taro' | ||
| 22 | +import { getTencentPreviewUrl } from '@/components/DocumentPreview/utils' | ||
| 23 | + | ||
| 24 | +// 响应式数据 | ||
| 25 | +const url = ref('') | ||
| 26 | +const fileType = ref('') | ||
| 27 | +const loading = ref(true) | ||
| 28 | + | ||
| 29 | +// 计算属性 | ||
| 30 | +const previewUrl = computed(() => { | ||
| 31 | + if (!url.value) return '' | ||
| 32 | + | ||
| 33 | + const decodedUrl = decodeURIComponent(url.value) | ||
| 34 | + | ||
| 35 | + // 根据文件类型选择预览方式 | ||
| 36 | + if (fileType.value === 'pdf') { | ||
| 37 | + // PDF 可以直接显示(需要支持跨域) | ||
| 38 | + return decodedUrl | ||
| 39 | + } else { | ||
| 40 | + // Office 文档使用腾讯文档预览 | ||
| 41 | + return getTencentPreviewUrl(decodedUrl) | ||
| 42 | + } | ||
| 43 | +}) | ||
| 44 | + | ||
| 45 | +// 页面加载 | ||
| 46 | +useLoad((options) => { | ||
| 47 | + console.log('文档预览页面参数:', options) | ||
| 48 | + | ||
| 49 | + if (options.url) { | ||
| 50 | + url.value = options.url | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + if (options.type) { | ||
| 54 | + fileType.value = decodeURIComponent(options.type) | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + // 设置导航栏标题 | ||
| 58 | + const titleMap = { | ||
| 59 | + pdf: 'PDF 预览', | ||
| 60 | + doc: 'Word 预览', | ||
| 61 | + docx: 'Word 预览', | ||
| 62 | + xls: 'Excel 预览', | ||
| 63 | + xlsx: 'Excel 预览', | ||
| 64 | + ppt: 'PPT 预览', | ||
| 65 | + pptx: 'PPT 预览' | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + const title = titleMap[fileType.value] || '文档预览' | ||
| 69 | + | ||
| 70 | + // #ifdef WEAPP | ||
| 71 | + Taro.setNavigationBarTitle({ title }) | ||
| 72 | + // #endif | ||
| 73 | +}) | ||
| 74 | + | ||
| 75 | +useReady(() => { | ||
| 76 | + console.log('文档预览页面 ready') | ||
| 77 | +}) | ||
| 78 | + | ||
| 79 | +/** | ||
| 80 | + * web-view 加载完成 | ||
| 81 | + */ | ||
| 82 | +const handleLoad = () => { | ||
| 83 | + console.log('web-view 加载完成') | ||
| 84 | + loading.value = false | ||
| 85 | + | ||
| 86 | + // #ifdef WEAPP | ||
| 87 | + Taro.hideLoading() | ||
| 88 | + // #endif | ||
| 89 | +} | ||
| 90 | + | ||
| 91 | +/** | ||
| 92 | + * web-view 错误 | ||
| 93 | + */ | ||
| 94 | +const handleError = (e) => { | ||
| 95 | + console.error('web-view 加载失败:', e) | ||
| 96 | + loading.value = false | ||
| 97 | + | ||
| 98 | + // #ifdef WEAPP | ||
| 99 | + Taro.hideLoading() | ||
| 100 | + Taro.showToast({ | ||
| 101 | + title: '预览加载失败', | ||
| 102 | + icon: 'none' | ||
| 103 | + }) | ||
| 104 | + // #endif | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +/** | ||
| 108 | + * 接收 web-view 消息 | ||
| 109 | + */ | ||
| 110 | +const handleMessage = (e) => { | ||
| 111 | + console.log('收到 web-view 消息:', e.detail.data) | ||
| 112 | +} | ||
| 113 | +</script> | ||
| 114 | + | ||
| 115 | +<style lang="less" scoped> | ||
| 116 | +.document-preview-page { | ||
| 117 | + width: 100%; | ||
| 118 | + height: 100vh; | ||
| 119 | + background: #fff; | ||
| 120 | +} | ||
| 121 | + | ||
| 122 | +// #ifdef WEAPP | ||
| 123 | +web-view { | ||
| 124 | + width: 100%; | ||
| 125 | + height: 100%; | ||
| 126 | +} | ||
| 127 | +// #endif | ||
| 128 | + | ||
| 129 | +// #ifdef H5 | ||
| 130 | +.preview-iframe { | ||
| 131 | + width: 100%; | ||
| 132 | + height: 100vh; | ||
| 133 | + border: none; | ||
| 134 | +} | ||
| 135 | +// #endif | ||
| 136 | +</style> |
-
Please register or login to post a comment