├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── docs_feedback.yml │ ├── feature_request.yml │ └── question.yml └── workflows │ └── static.yml ├── .gitignore ├── LICENSE ├── README.en.md ├── README.md ├── demos ├── vue2-demo │ ├── .gitignore │ ├── .npmrc │ ├── README.md │ ├── babel.config.js │ ├── jsconfig.json │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ └── logo.png │ │ ├── main.js │ │ ├── router │ │ │ └── index.js │ │ ├── style.css │ │ └── views │ │ │ ├── PrintAsyncUrl.vue │ │ │ ├── PrintBasic.vue │ │ │ ├── PrintBasicTwo.vue │ │ │ ├── PrintCanvas.vue │ │ │ ├── PrintForm.vue │ │ │ ├── PrintIframe.vue │ │ │ └── PrintUrl.vue │ └── vue.config.js └── vue3-demo │ ├── .gitignore │ ├── .vscode │ └── extensions.json │ ├── README.md │ ├── index.html │ ├── package.json │ ├── public │ ├── vite.svg │ └── vue3-print.pdf │ ├── src │ ├── App.vue │ ├── assets │ │ ├── shared-styles.css │ │ ├── styles │ │ │ └── common.css │ │ └── vue.svg │ ├── components │ │ └── PrintPageLayout.vue │ ├── main.ts │ ├── router │ │ └── index.ts │ ├── style.css │ ├── utils │ │ └── common.ts │ ├── views │ │ ├── CustomPreviewTools.vue │ │ ├── PrintAdvanced.vue │ │ ├── PrintAsyncUrl.vue │ │ ├── PrintBaiduMap.vue │ │ ├── PrintBasic.vue │ │ ├── PrintBasicTwo.vue │ │ ├── PrintCallbacks.vue │ │ ├── PrintCanvas.vue │ │ ├── PrintForm.vue │ │ ├── PrintIframe.vue │ │ ├── PrintPaperSizes.vue │ │ ├── PrintPdf.vue │ │ ├── PrintPreviewSize.vue │ │ ├── PrintTable.vue │ │ └── PrintUrl.vue │ └── vite-env.d.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── docs ├── .vitepress │ └── config.mts ├── guide │ ├── api.md │ ├── faq.md │ ├── getting-started.md │ ├── impl-principle.md │ ├── life-cycle.md │ ├── migration-from-vue-print-nb.md │ ├── preview-tools.md │ └── what-is-vue-print-next.md ├── index.md ├── package.json └── public │ └── logo.png ├── index.html ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── public ├── favicon.ico └── logo.png ├── src ├── App.vue ├── assets │ └── logo.png ├── components │ └── HelloWorld.vue ├── index.css ├── main.ts ├── print │ ├── index.ts │ └── packages │ │ ├── VuePrintNext.ts │ │ └── vPrint.ts └── vite-env.d.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── types └── index.d.ts └── vite.config.ts /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: '🐞 Bug report' 2 | description: Create a report to help us improve / 创建报告以帮助我们改进 3 | title: '[Bug]: ' 4 | labels: ['status: waiting for maintainer'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Report errors and exceptions found in the project. 10 | 11 | Before submitting a new bug/issue, please check the links below to see if there is a solution or if the issue has already been reported: 12 | 13 | 在提交新 issue 之前,先通过以下链接检查是否存在相同问题: 14 | 15 | > [Issues](https://github.com/Alessandro-Pang/vue-print-next/issues) | [Closed Issues](https://github.com/Alessandro-Pang/vue-print-next/issues?q=is%3Aissue+is%3Aclosed) 16 | 17 | - type: textarea 18 | id: description 19 | attributes: 20 | label: Describe the bug / 问题描述 21 | placeholder: | 22 | Please provide a clear and concise description of the bug you encountered. Include screenshots if they help explain the problem better. 23 | 请提供清晰简洁的问题描述。如果可以的话,请添加截图以便更好地说明问题。 24 | 25 | If you need to share code, please use Markdown code block syntax as shown below: 26 | 如果包含代码块,请使用下面展示的 Markdown 代码块语法: 27 | 28 | ```javascript 29 | // Your code here 30 | ``` 31 | validations: 32 | required: true 33 | 34 | - type: input 35 | id: link 36 | attributes: 37 | label: Reproduction link / 复现链接 38 | placeholder: | 39 | Provide a link to a minimal reproduction of the issue, such as on CodeSandbox or StackBlitz. 40 | CodeSandbox / StackBlitz / ... 41 | 42 | 提供一个最小复现问题的链接,例如 CodeSandbox 或 StackBlitz。 43 | validations: 44 | required: false 45 | 46 | - type: textarea 47 | id: steps 48 | attributes: 49 | label: Steps to Reproduce the Bug or Issue / 重现步骤 50 | placeholder: | 51 | List the steps to reproduce the bug. For example: 52 | 1. Go to '...' 53 | 2. Click on '...' 54 | 3. Scroll down to '...' 55 | 4. See error 56 | validations: 57 | required: false 58 | 59 | - type: dropdown 60 | id: version 61 | attributes: 62 | label: Version / 版本 63 | options: 64 | - Please select / 请选择 65 | - 1.x 66 | - 0.x 67 | validations: 68 | required: true 69 | 70 | - type: checkboxes 71 | id: OS 72 | attributes: 73 | label: OS / 操作系统 74 | options: 75 | - label: macOS 76 | - label: Windows 77 | - label: Linux 78 | - label: Others / 其他 79 | validations: 80 | required: true 81 | 82 | - type: checkboxes 83 | id: Browser 84 | attributes: 85 | label: Browser / 浏览器 86 | options: 87 | - label: Chrome 88 | - label: Edge 89 | - label: Firefox 90 | - label: Safari (Limited support / 有限支持) 91 | - label: IE (Nonsupport / 不支持) 92 | - label: Others / 其他 93 | validations: 94 | required: true 95 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs_feedback.yml: -------------------------------------------------------------------------------- 1 | name: '📚 Docs Feedback' 2 | description: Improve documentation to make it more user-friendly and accessible / 助力打造更易于上手操作以及便捷查阅的文档 3 | labels: ['status: waiting for maintainer', 'docs-feedback'] 4 | title: '[docs] ' 5 | body: 6 | - type: input 7 | id: page-url 8 | attributes: 9 | label: Related Page / 相关页面 10 | description: Which page of the documentation is this about? / 这是关于文档的哪个页面? 11 | placeholder: https://alexpang.cn/vue-print-next/docs/ 12 | validations: 13 | required: true 14 | - type: dropdown 15 | id: issue-kind 16 | attributes: 17 | label: Kind of Issue / 问题类型 18 | description: What kind of problem are you facing? / 您遇到了什么类型的问题? 19 | options: 20 | - Unclear explanations / 解释不清 21 | - Missing information / 信息缺失 22 | - Broken demo / 示例损坏 23 | - Other / 其他 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: issue-description 28 | attributes: 29 | label: Issue Description / 问题描述 30 | description: | 31 | Let us know what went wrong when you were using this documentation and what we could do to improve it / 请告知您在使用此文档时遇到的问题以及我们可以改进的地方 32 | placeholder: | 33 | Please describe the issue in detail. / 请详细描述问题。 34 | - type: textarea 35 | id: context 36 | attributes: 37 | label: Context / 上下文 38 | description: What are you trying to accomplish? Providing context helps us come up with a solution that is more useful in the real world / 您希望实现什么目标?提供上下文有助于我们提出更实用的解决方案 39 | placeholder: | 40 | Describe your goal or task. / 描述您的目标或任务。 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: '💡 Feature Request' 2 | description: I have a suggestion (and may want to implement it) / 我有一个建议(或者想参与贡献) 3 | title: '[Feat]: ' 4 | labels: ['status: waiting for maintainer'] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Describe the feature / 功能描述 10 | validations: 11 | required: true 12 | 13 | - type: dropdown 14 | attributes: 15 | label: Are you willing to contribute? / 是否愿意参与贡献? 16 | options: 17 | - Please select / 请选择 18 | - ✅ Yes / 是 19 | - ❌ No / 否 20 | validations: 21 | required: true 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: '💬 Question' 3 | description: Ask questions about the project / 提问关于项目的问题 4 | title: '[Ques]: ' 5 | labels: ['status: waiting for maintainer'] 6 | body: 7 | - type: textarea 8 | id: description 9 | attributes: 10 | label: Describe the Question / 问题描述 11 | placeholder: | 12 | Please provide a clear and concise description of the question you have. / 请提供清晰简洁的问题描述。 13 | Include any relevant context or details that might help in understanding your question. / 包含任何可能有助于理解您问题的相关背景或细节。 14 | 15 | --- 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ['master'] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: 'pages' 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Setup Pages 35 | uses: actions/configure-pages@v4 36 | - run: npm install -g pnpm@^7 37 | - run: pnpm install 38 | - run: pnpm build 39 | - run: pnpm -r build 40 | - run: mkdir web && cp -r ./demos/vue3-demo/dist ./web/vue3-demo && cp -r ./docs/.vitepress/dist ./web/docs 41 | - name: Upload artifact 42 | uses: actions/upload-pages-artifact@v3 43 | with: 44 | # Upload entire repository 45 | path: './web' 46 | - name: Deploy to GitHub Pages 47 | id: deployment 48 | uses: actions/deploy-pages@v4 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | docs/.vitepress/cache 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 zi.yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | logo 3 |

Vue Print Next

4 | 5 | NPM Version 6 | 7 | 8 | NPM Downloads 9 | 10 | 11 | npm bundle size 12 | 13 |
14 | 15 | ![Alt](https://repobeats.axiom.co/api/embed/d78c098d0c6aded6d25e2603961030f7a1a96e64.svg "Repobeats analytics image") 16 | 17 | [English](./README.en.md) | 简体中文 18 | 19 | > Vue 打印插件,简单、快速、方便、轻巧,支持 Vue 2 和 Vue 3。 20 | 21 | 本插件基于 [vue3-print-nb](https://github.com/Power-kxLee/vue3-print-nb) 开发,并使用 TypeScript 完全重写,以更好地支持 Vue 3 的 setup 函数和组合式 API。 22 | 23 | ## 📚 文档 24 | 25 | 在线文档:[https://alexpang.cn/vue-print-next/docs](https://alexpang.cn/vue-print-next/docs) 26 | 27 | ## ✨ 特性 28 | 29 | - 支持 Vue 2 和 Vue 3,兼容性强 30 | - 支持指令调用和手动调用 `VuePrintNext` 方法进行打印 31 | - 完全支持 Vue 3 的 setup 函数和组合式 API 32 | - 支持全局和局部内容打印,以及打印预览功能 33 | - 支持设置指定 class 样式的元素忽略打印 34 | - 支持通过 css 选择器、手动传入 Dom 节点进行局部打印 35 | - 支持自定义纸张尺寸和方向 36 | - 支持深色模式和窗口模式 37 | - 支持打印工具栏自定义配置 38 | - 支持响应式设计,适配不同设备 39 | - 提供丰富的回调函数,满足各种打印场景需求 40 | 41 | ## 🔍 Demo 42 | 43 | 项目中提供了 Vue2 和 Vue3 的 demo 源码,可以 clone 下本项目后进行查看 44 | 45 | - **Vue2:** /demos/vue2-demo 46 | - **Vue3:** /demos/vue3-demo 47 | 48 | 在线 demo:[https://alexpang.cn/vue-print-next/vue3-demo](https://alexpang.cn/vue-print-next/vue3-demo) 49 | 50 | ## 📦 安装 51 | 52 | 你可以通过 npm、yarn 或 pnpm 安装该插件: 53 | 54 | ```bash 55 | npm install vue-print-next --save 56 | # or 57 | yarn add vue-print-next 58 | # or 59 | pnpm add vue-print-next 60 | ``` 61 | 62 | ## 🚀 快速开始 63 | 64 | ### 1. 全局使用插件 65 | 66 | 在你的 `main.ts` 或 `main.js` 文件中: 67 | 68 | ```typescript 69 | import {createApp} from 'vue'; 70 | import App from './App.vue'; 71 | import {printPlugin} from 'vue-print-next'; 72 | 73 | const app = createApp(App); 74 | app.use(printPlugin); 75 | app.mount('#app'); 76 | ``` 77 | 78 | ### 2. Vue3 在组件中使用指令 79 | 80 | ```html 81 | 85 | 86 | 96 | ``` 97 | 98 | ### 3. Vue2 在组件中使用指令 99 | 100 | ```html 101 | 111 | 112 | 122 | ``` 123 | 124 | ### 4. 使用 `VuePrintNext` 类 125 | 126 | 如果你需要更复杂的打印逻辑,可以直接使用 `VuePrintNext` 类: 127 | 128 | ```html 129 | 136 | 137 | 145 | ``` 146 | 147 | ## 📋 API 详解 148 | 149 | ### `vPrint` 指令 150 | 151 | - **全屏打印**:`` 152 | - **局部打印**:``,其中 `#printMe` 是需要打印的 DOM 元素选择器。 153 | 154 | ### `VuePrintNext` 类 155 | 156 | 用于手动调用打印功能。 157 | 158 | #### 参数说明 159 | 160 | | 参数 | 类型 | 说明 | 默认值 | 161 | |-----------------------------|---------------------------|-------------------------------------|------------| 162 | | `el` | `string` \| `HtmlElement` | 需要打印的元素,支持 css 选择器或 dom 节点 | - | 163 | | `standard` | `string` | 文档类型,默认是html5,可选 html5,loose,strict | 'html5' | 164 | | `noPrintSelector` | `string[]` \| `string` | 打印时需要忽略的 css 选择器 | - | 165 | | `popTitle` | `string` | 打印时的页眉 | 默认当前 title | 166 | | `preview` | `boolean` | 是否启用打印预览功能 | `false` | 167 | | `previewTitle` | `string` | 预览窗口的标题 | '打印预览' | 168 | | `previewPrintBtnLabel` | `string` | 预览窗口中的打印按钮标签 | '打印' | 169 | | `extraCss` | `string` | 额外的 CSS 文件路径 | - | 170 | | `extraHead` | `string` | 额外的 `` 内容 | - | 171 | | `url` | `string` | 打印指定的网址内容 | - | 172 | | `asyncUrl` | `function` | 异步加载 URL 内容的方法 | - | 173 | | `zIndex` | `number` | 预览窗口的 `z-index`值 | 20002 | 174 | | `paperSize` | `string` | 纸张尺寸,可选值包括 'A0' 到 'A8'、'Letter'、'Legal'、'Tabloid'、'custom' | 'A4' | 175 | | `orientation` | `string` | 纸张方向,可选值为 'portrait'(纵向)或 'landscape'(横向) | 'portrait' | 176 | | `customSize` | `object` | 自定义纸张尺寸,仅当 paperSize 为 'custom' 时生效 | - | 177 | | `darkMode` | `boolean` | 预览窗口是否默认使用深色模式 | `false` | 178 | | `windowMode` | `boolean` | 预览窗口是否默认使用弹窗模式(非全屏) | `false` | 179 | | `defaultScale` | `number` | 预览窗口默认缩放比例 | 1 | 180 | | `previewTools` | `object \| boolean` | 预览工具栏配置,控制显示哪些工具按钮(zoom、theme、fullscreen) | `{ zoom: true, theme: true, fullscreen: true }` | 181 | | `openCallback` | `function` | 打印窗口打开时的回调 | - | 182 | | `closeCallback` | `function` | 打印窗口关闭时的回调 | - | 183 | | `beforeOpenCallback` | `function` | 打印窗口打开前的回调(打印预览使用) | - | 184 | | `previewBeforeOpenCallback` | `function` | 预览框架 iframe 加载前的回调(预览使用) | - | 185 | | `previewOpenCallback` | `function` | 预览框架 iframe 加载完成后的回调(预览使用) | - | 186 | 187 | ## 🌰 使用示例 188 | 189 | ### 打印整个页面 190 | 191 | ```html 192 | 193 | ``` 194 | 195 | ### 打印局部内容 196 | 197 | 通过指定 `id` 参数打印局部内容: 198 | 199 | ```html 200 |
201 |

这是需要打印的内容

202 |
203 | 204 | 205 | ``` 206 | 207 | ### 使用 ref 获取打印元素 208 | 209 | 允许传入一个 dom 节点,如下,可以通过 `ref` 获取打印元素 210 | 211 | ```html 212 | 222 | 223 | 230 | ``` 231 | 232 | ### 传递对象参数 233 | 234 | ```html 235 | 244 | 245 | 258 | ``` 259 | 260 | ### 打印 URL 261 | 262 | 通过指定 URL 打印,并确保你的 URL 符合同源策略: 263 | 264 | ```html 265 | 268 | 269 | 274 | ``` 275 | 276 | ### 忽略不需要打印的元素 277 | 278 | 通过设置 `noPrintSelector` 参数忽略不需要打印的元素: 279 | 280 | ```html 281 | 292 | 293 | 300 | ``` 301 | 302 | ### 异步加载 URL 内容 303 | 304 | 如果你的 URL 需要异步加载,可以使用以下方法: 305 | 306 | ```html 307 | 310 | 311 | 320 | ``` 321 | 322 | ### 设置纸张尺寸和方向 323 | 324 | 可以通过 `paperSize` 和 `orientation` 参数设置打印纸张的尺寸和方向: 325 | 326 | ```html 327 | 333 | 334 | 342 | ``` 343 | 344 | ### 自定义纸张尺寸 345 | 346 | 当需要使用非标准纸张尺寸时,可以设置 `paperSize` 为 `'custom'` 并提供 `customSize` 参数: 347 | 348 | ```html 349 | 355 | 356 | 368 | ``` 369 | 370 | ### 深色模式和窗口模式 371 | 372 | 可以通过 `darkMode` 和 `windowMode` 参数设置预览界面的显示模式: 373 | 374 | ```html 375 | 381 | 382 | 391 | ``` 392 | 393 | ### 自定义预览工具栏 394 | 395 | 可以通过 `previewTools` 参数自定义预览工具栏的显示: 396 | 397 | ```html 398 | 404 | 405 | 417 | ``` 418 | 419 | ## 🤝 贡献指南 420 | 421 | 1. Fork 本仓库 422 | 2. 创建你的特性分支 (`git checkout -b feature/amazing-feature`) 423 | 3. 提交你的更改 (`git commit -m 'Add some amazing feature'`) 424 | 4. 推送到分支 (`git push origin feature/amazing-feature`) 425 | 5. 开启一个 Pull Request 426 | 427 | ## ⭐ Star History 428 | 429 | 430 | 431 | 432 | 433 | Star History Chart 434 | 435 | 436 | 437 | ## 👥 Supporters 438 | 439 | 440 | 441 | 442 | 443 | Star History 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | Fork History 452 | 453 | 454 | 455 | ## 📄 License 456 | 457 | [MIT](http://opensource.org/licenses/MIT) 458 | 459 | --- 460 | 461 | 欢迎在 [GitHub Issues](https://github.com/Alessandro-Pang/vue-print-next/issues) 上讨论并提出问题或提交 Pull Request! 462 | -------------------------------------------------------------------------------- /demos/vue2-demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /demos/vue2-demo/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /demos/vue2-demo/README.md: -------------------------------------------------------------------------------- 1 | # vue2-demo 2 | 3 | ## Project setup 4 | ``` 5 | pnpm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | pnpm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | pnpm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | pnpm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /demos/vue2-demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /demos/vue2-demo/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "baseUrl": "./", 6 | "moduleResolution": "node", 7 | "paths": { 8 | "@/*": [ 9 | "src/*" 10 | ] 11 | }, 12 | "lib": [ 13 | "esnext", 14 | "dom", 15 | "dom.iterable", 16 | "scripthost" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demos/vue2-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue2-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.8.3", 12 | "echarts": "^5.5.1", 13 | "vue": "^2.6.14", 14 | "vue-print-next": "workspace:*", 15 | "vue-router": "^3.6.5" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.12.16", 19 | "@babel/eslint-parser": "^7.12.16", 20 | "@vue/cli-plugin-babel": "~5.0.0", 21 | "@vue/cli-plugin-eslint": "~5.0.0", 22 | "@vue/cli-service": "~5.0.0", 23 | "eslint": "^7.32.0", 24 | "eslint-plugin-vue": "^8.0.3", 25 | "vue-template-compiler": "^2.6.14" 26 | }, 27 | "eslintConfig": { 28 | "root": true, 29 | "env": { 30 | "node": true 31 | }, 32 | "extends": [ 33 | "plugin:vue/essential", 34 | "eslint:recommended" 35 | ], 36 | "parserOptions": { 37 | "parser": "@babel/eslint-parser" 38 | }, 39 | "rules": {} 40 | }, 41 | "browserslist": [ 42 | "> 1%", 43 | "last 2 versions", 44 | "not dead" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /demos/vue2-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alessandro-Pang/vue-print-next/af8f1d342e4845cf991de66a8fbe00862d106abc/demos/vue2-demo/public/favicon.ico -------------------------------------------------------------------------------- /demos/vue2-demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demos/vue2-demo/src/App.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 36 | 37 | 97 | -------------------------------------------------------------------------------- /demos/vue2-demo/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alessandro-Pang/vue-print-next/af8f1d342e4845cf991de66a8fbe00862d106abc/demos/vue2-demo/src/assets/logo.png -------------------------------------------------------------------------------- /demos/vue2-demo/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import {printPlugin} from "vue-print-next"; 4 | import './style.css' 5 | import router from "./router"; 6 | import VueRouter from "vue-router"; 7 | 8 | Vue.config.productionTip = false 9 | 10 | Vue.use(printPlugin) 11 | Vue.use(VueRouter) 12 | 13 | new Vue({ 14 | router, 15 | render: h => h(App), 16 | }).$mount('#app') 17 | -------------------------------------------------------------------------------- /demos/vue2-demo/src/router/index.js: -------------------------------------------------------------------------------- 1 | import VueRouter from 'vue-router' 2 | 3 | import PrintBasic from '../views/PrintBasic.vue' 4 | import PrintBasic2 from '../views/PrintBasicTwo.vue' 5 | import PrintCanvas from '../views/PrintCanvas.vue' 6 | import PrintAsyncUrl from '../views/PrintAsyncUrl.vue' 7 | import PrintUrl from '../views/PrintUrl.vue' 8 | import PrintForm from '../views/PrintForm.vue' 9 | import PrintIframe from '../views/PrintIframe.vue' 10 | export const routes = [ 11 | {path: '/', redirect: '/print-basic'}, 12 | {path: '/print-basic', component: PrintBasic, meta: {title:'基本打印用例'}}, 13 | {path: '/print-basic2', component: PrintBasic2, meta: {title:'基本打印用例2'}}, 14 | {path: '/print-canvas', component: PrintCanvas, meta: {title:'Canvas 打印示例'}}, 15 | {path: '/print-url', component: PrintUrl, meta: {title:'Url 打印示例'}}, 16 | {path: '/print-async-url', component: PrintAsyncUrl, meta: {title:'异步 URL 打印示例'}}, 17 | {path: '/print-form', component: PrintForm, meta: {title:'Form 表单打印示例'}}, 18 | {path: '/print-iframe', component: PrintIframe, meta: {title:'Iframe 嵌套打印示例'}}, 19 | ] 20 | 21 | const router = new VueRouter({ 22 | mode: 'history', 23 | routes, 24 | }) 25 | 26 | export default router 27 | -------------------------------------------------------------------------------- /demos/vue2-demo/src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | font-synthesis: none; 7 | text-rendering: optimizeLegibility; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | a { 13 | font-weight: 500; 14 | color: #646cff; 15 | text-decoration: inherit; 16 | } 17 | a:hover { 18 | color: #747bff; 19 | } 20 | 21 | html, body { 22 | height: 100%; 23 | width: 100%; 24 | margin: 0; 25 | padding: 0; 26 | -webkit-print-color-adjust: exact; 27 | -moz-print-color-adjust: exact; 28 | -ms-print-color-adjust: exact; 29 | print-color-adjust: exact; 30 | } 31 | 32 | h1 { 33 | font-size: 3.2em; 34 | line-height: 1.1; 35 | } 36 | 37 | button { 38 | border-radius: 8px; 39 | border: 1px solid transparent; 40 | padding: 0.6em 1.2em; 41 | font-size: 1em; 42 | font-weight: 500; 43 | font-family: inherit; 44 | background-color: #f9f9f9; 45 | cursor: pointer; 46 | transition: border-color 0.25s; 47 | } 48 | 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | .card { 58 | padding: 2em; 59 | } 60 | 61 | * { 62 | box-sizing: border-box; 63 | } 64 | 65 | #app { 66 | margin: 0 auto; 67 | text-align: center; 68 | height: 100%; 69 | width: 100%; 70 | } 71 | 72 | 73 | .a4-page { 74 | width: 595px; 75 | height: 842px; 76 | } 77 | 78 | .mt-m { 79 | margin-top: 1rem; 80 | } 81 | -------------------------------------------------------------------------------- /demos/vue2-demo/src/views/PrintAsyncUrl.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /demos/vue2-demo/src/views/PrintBasic.vue: -------------------------------------------------------------------------------- 1 | 39 | 60 | 84 | -------------------------------------------------------------------------------- /demos/vue2-demo/src/views/PrintBasicTwo.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 35 | 37 | -------------------------------------------------------------------------------- /demos/vue2-demo/src/views/PrintCanvas.vue: -------------------------------------------------------------------------------- 1 | 199 | 200 | 206 | 207 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /demos/vue2-demo/src/views/PrintForm.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 97 | 99 | -------------------------------------------------------------------------------- /demos/vue2-demo/src/views/PrintIframe.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 14 | 16 | -------------------------------------------------------------------------------- /demos/vue2-demo/src/views/PrintUrl.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 30 | 31 | 34 | -------------------------------------------------------------------------------- /demos/vue2-demo/vue.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('@vue/cli-service') 2 | module.exports = defineConfig({ 3 | transpileDependencies: true 4 | }) 5 | -------------------------------------------------------------------------------- /demos/vue3-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /demos/vue3-demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /demos/vue3-demo/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + TypeScript + Vite 2 | 3 | This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /demos/vue3-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-demo", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vue-tsc -b && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "echarts": "^5.5.1", 13 | "vue": "^3.4.29", 14 | "vue-print-next": "workspace:*", 15 | "vue-router": "4" 16 | }, 17 | "devDependencies": { 18 | "@vitejs/plugin-vue": "^5.0.5", 19 | "typescript": "^5.2.2", 20 | "vite": "^5.3.1", 21 | "vue-tsc": "^2.0.21" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demos/vue3-demo/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demos/vue3-demo/public/vue3-print.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alessandro-Pang/vue-print-next/af8f1d342e4845cf991de66a8fbe00862d106abc/demos/vue3-demo/public/vue3-print.pdf -------------------------------------------------------------------------------- /demos/vue3-demo/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 26 | 27 | 86 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/assets/shared-styles.css: -------------------------------------------------------------------------------- 1 | /* 共享样式文件 - 用于统一所有打印示例页面的样式 */ 2 | 3 | /* 卡片容器样式 */ 4 | .print-container { 5 | max-width: 900px; 6 | margin: 0 auto; 7 | padding: 12px; 8 | background-color: #ffffff; 9 | border-radius: 12px; 10 | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); 11 | transition: all 0.3s ease; 12 | } 13 | 14 | /* 页面标题样式 */ 15 | .page-title { 16 | font-size: 1.8rem; 17 | font-weight: 600; 18 | color: #333; 19 | margin-bottom: 1.5rem; 20 | position: relative; 21 | padding-bottom: 0.5rem; 22 | } 23 | 24 | .page-title::after { 25 | content: ''; 26 | position: absolute; 27 | bottom: 0; 28 | left: 0; 29 | width: 60px; 30 | height: 3px; 31 | background-image: linear-gradient(45deg, #fc4a1a, #b21f1f, #fdbb2d); 32 | border-radius: 3px; 33 | } 34 | 35 | /* 设置面板样式 */ 36 | .settings-panel { 37 | background-color: #f8f9fa; 38 | border-radius: 8px; 39 | padding: 20px; 40 | margin-bottom: 20px; 41 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); 42 | transition: all 0.3s ease; 43 | } 44 | 45 | .settings-panel:hover { 46 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 47 | } 48 | 49 | /* 设置组样式 */ 50 | .setting-group { 51 | margin-bottom: 15px; 52 | padding-bottom: 15px; 53 | border-bottom: 1px solid #eee; 54 | } 55 | 56 | .setting-group:last-child { 57 | border-bottom: none; 58 | margin-bottom: 0; 59 | padding-bottom: 0; 60 | } 61 | 62 | .setting-group label { 63 | display: block; 64 | margin-bottom: 8px; 65 | font-weight: 500; 66 | color: #444; 67 | } 68 | 69 | /* 输入控件样式 */ 70 | .select-input, .text-input, .number-input { 71 | width: 100%; 72 | padding: 10px 12px; 73 | border: 1px solid #ddd; 74 | border-radius: 6px; 75 | font-size: 14px; 76 | transition: all 0.3s; 77 | background-color: #fff; 78 | } 79 | 80 | .select-input:focus, .text-input:focus, .number-input:focus { 81 | border-color: #646cff; 82 | box-shadow: 0 0 0 3px rgba(100, 108, 255, 0.2); 83 | outline: none; 84 | } 85 | 86 | /* 单选和复选框组样式 */ 87 | .radio-group, .checkbox-group { 88 | display: flex; 89 | gap: 20px; 90 | flex-wrap: wrap; 91 | align-items: center; 92 | } 93 | 94 | .radio-group label, .checkbox-group label { 95 | display: flex; 96 | align-items: center; 97 | cursor: pointer; 98 | margin-bottom: 0; 99 | } 100 | 101 | .radio-group input, .checkbox-group input { 102 | width: 20px; 103 | margin-right: 8px; 104 | } 105 | 106 | /* 范围滑块样式 */ 107 | .range-input { 108 | width: 100%; 109 | height: 6px; 110 | -webkit-appearance: none; 111 | background: #e0e0e0; 112 | border-radius: 3px; 113 | outline: none; 114 | margin: 10px 0; 115 | } 116 | 117 | .range-input::-webkit-slider-thumb { 118 | -webkit-appearance: none; 119 | width: 18px; 120 | height: 18px; 121 | background: #646cff; 122 | border-radius: 50%; 123 | cursor: pointer; 124 | transition: all 0.2s; 125 | } 126 | 127 | .range-input::-webkit-slider-thumb:hover { 128 | transform: scale(1.1); 129 | background: #747bff; 130 | } 131 | 132 | /* 按钮样式 */ 133 | .print-btn { 134 | padding: 10px 16px; 135 | border: none; 136 | border-radius: 6px; 137 | font-weight: 500; 138 | cursor: pointer; 139 | transition: all 0.3s; 140 | margin: 0 8px 8px 0; 141 | background-color: #646cff; 142 | color: white; 143 | } 144 | 145 | .print-btn:hover { 146 | background-color: #747bff; 147 | transform: translateY(-2px); 148 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 149 | } 150 | 151 | .print-btn.primary { 152 | background-color: #1890ff; 153 | } 154 | 155 | .print-btn.primary:hover { 156 | background-color: #40a9ff; 157 | } 158 | 159 | .print-btn.secondary { 160 | background-color: #52c41a; 161 | } 162 | 163 | .print-btn.secondary:hover { 164 | background-color: #73d13d; 165 | } 166 | 167 | .print-btn.accent { 168 | background-color: #722ed1; 169 | } 170 | 171 | .print-btn.accent:hover { 172 | background-color: #9254de; 173 | } 174 | 175 | /* 打印内容区域样式 */ 176 | .print-content { 177 | border: 1px solid #e0e0e0; 178 | border-radius: 8px; 179 | padding: 20px; 180 | background-color: #fff; 181 | transition: all 0.3s ease; 182 | } 183 | 184 | /* 打印部分样式 */ 185 | .print-section { 186 | margin-bottom: 30px; 187 | padding-bottom: 20px; 188 | border-bottom: 1px dashed #d9d9d9; 189 | } 190 | 191 | .print-section:last-child { 192 | border-bottom: none; 193 | margin-bottom: 0; 194 | padding-bottom: 0; 195 | } 196 | 197 | /* 特性卡片样式 */ 198 | .feature-card { 199 | background-color: #f9f9fa; 200 | border-radius: 8px; 201 | padding: 20px; 202 | margin-top: 15px; 203 | box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04); 204 | transition: all 0.3s ease; 205 | } 206 | 207 | .feature-card:hover { 208 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); 209 | transform: translateY(-2px); 210 | } 211 | 212 | /* 代码示例样式 */ 213 | .code-example { 214 | background-color: #f5f5f5; 215 | border-radius: 6px; 216 | padding: 15px; 217 | margin-top: 15px; 218 | overflow-x: auto; 219 | border-left: 3px solid #646cff; 220 | text-align: left; 221 | } 222 | 223 | .code-example pre { 224 | margin: 0; 225 | font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; 226 | font-size: 14px; 227 | line-height: 1.5; 228 | } 229 | 230 | code { 231 | background-color: #f0f0f0; 232 | padding: 3px 6px; 233 | border-radius: 4px; 234 | font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; 235 | color: #d56161; 236 | font-size: 0.9em; 237 | } 238 | 239 | /* 帮助文本样式 */ 240 | .help-text { 241 | font-size: 13px; 242 | color: #666; 243 | margin-top: 6px; 244 | line-height: 1.4; 245 | text-align: left; 246 | } 247 | 248 | /* 响应式设计 */ 249 | @media (max-width: 768px) { 250 | .print-container { 251 | padding: 15px; 252 | } 253 | 254 | .settings-panel { 255 | padding: 15px; 256 | } 257 | 258 | .custom-size-inputs { 259 | grid-template-columns: 1fr; 260 | } 261 | 262 | .buttons-group, .section-buttons { 263 | flex-direction: column; 264 | } 265 | 266 | .print-btn { 267 | width: 100%; 268 | margin-right: 0; 269 | } 270 | } 271 | 272 | /* 深色模式预览样式 */ 273 | .preview-example { 274 | border: 1px solid #d9d9d9; 275 | border-radius: 8px; 276 | padding: 15px; 277 | margin-top: 15px; 278 | background-color: #fff; 279 | transition: all 0.3s ease; 280 | } 281 | 282 | .preview-example.dark { 283 | background-color: #333; 284 | color: #fff; 285 | border-color: #555; 286 | } 287 | 288 | /* 表格样式 */ 289 | table { 290 | width: 100%; 291 | border-collapse: collapse; 292 | margin: 15px 0; 293 | border-radius: 6px; 294 | overflow: hidden; 295 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); 296 | } 297 | 298 | table th { 299 | background-color: #f5f5f5; 300 | padding: 12px; 301 | text-align: left; 302 | font-weight: 600; 303 | color: #333; 304 | border: 1px solid #ddd; 305 | } 306 | 307 | table td { 308 | padding: 10px 12px; 309 | border: 1px solid #ddd; 310 | transition: background-color 0.2s; 311 | } 312 | 313 | table tr:nth-child(even) { 314 | background-color: #f9f9f9; 315 | } 316 | 317 | table tr:hover td { 318 | background-color: #f0f7ff; 319 | } 320 | 321 | table caption { 322 | margin-bottom: 10px; 323 | font-weight: 600; 324 | color: #333; 325 | } 326 | 327 | table tfoot { 328 | background-color: #f5f5f5; 329 | font-weight: 500; 330 | } 331 | 332 | /* 自定义尺寸输入组 */ 333 | .custom-size-inputs { 334 | display: grid; 335 | grid-template-columns: 1fr 1fr 1fr; 336 | gap: 15px; 337 | margin-top: 10px; 338 | } 339 | 340 | /* 纸张可视化 */ 341 | .paper-visualization { 342 | display: flex; 343 | justify-content: center; 344 | margin: 20px 0; 345 | } 346 | 347 | .paper-preview { 348 | position: relative; 349 | border: 1px solid #ddd; 350 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 351 | display: flex; 352 | flex-direction: column; 353 | justify-content: center; 354 | align-items: center; 355 | transition: all 0.3s ease; 356 | } 357 | 358 | .paper-preview:hover { 359 | box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); 360 | transform: translateY(-5px); 361 | } 362 | 363 | .paper-label { 364 | font-weight: 500; 365 | margin-bottom: 10px; 366 | } 367 | 368 | .paper-orientation { 369 | font-size: 14px; 370 | color: #666; 371 | } 372 | 373 | /* 动画效果 */ 374 | @keyframes fadeIn { 375 | from { opacity: 0; transform: translateY(10px); } 376 | to { opacity: 1; transform: translateY(0); } 377 | } 378 | 379 | .fade-in { 380 | animation: fadeIn 0.3s ease-out forwards; 381 | } -------------------------------------------------------------------------------- /demos/vue3-demo/src/assets/styles/common.css: -------------------------------------------------------------------------------- 1 | /* 2 | * 统一样式文件 - vue-print-next 演示项目 3 | * 为所有示例页面提供一致的设计风格 4 | */ 5 | 6 | /* 基础变量 */ 7 | :root { 8 | --primary-color: #3a7bd5; 9 | --primary-gradient: linear-gradient(45deg, #3a7bd5, #00d2ff); 10 | --secondary-color: #6c5ce7; 11 | --secondary-gradient: linear-gradient(45deg, #6c5ce7, #a29bfe); 12 | --accent-color: #fd746c; 13 | --accent-gradient: linear-gradient(45deg, #fd746c, #ff9068); 14 | --success-color: #0ba360; 15 | --success-gradient: linear-gradient(45deg, #0ba360, #3cba92); 16 | --warning-color: #f2994a; 17 | --warning-gradient: linear-gradient(45deg, #f2994a, #f2c94c); 18 | --danger-color: #eb3349; 19 | --danger-gradient: linear-gradient(45deg, #eb3349, #f45c43); 20 | --text-primary: #333333; 21 | --text-secondary: #666666; 22 | --text-tertiary: #999999; 23 | --bg-light: #f8f9fa; 24 | --bg-white: #ffffff; 25 | --border-color: #e0e0e0; 26 | --border-radius-sm: 4px; 27 | --border-radius-md: 8px; 28 | --border-radius-lg: 12px; 29 | --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.05); 30 | --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08); 31 | --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12); 32 | --transition-fast: 0.2s ease; 33 | --transition-normal: 0.3s ease; 34 | --transition-slow: 0.5s ease; 35 | --spacing-xs: 4px; 36 | --spacing-sm: 8px; 37 | --spacing-md: 16px; 38 | --spacing-lg: 24px; 39 | --spacing-xl: 32px; 40 | } 41 | 42 | /* 通用容器样式 */ 43 | .print-container { 44 | padding: var(--spacing-lg); 45 | border-radius: var(--border-radius-lg); 46 | background-color: var(--bg-white); 47 | box-shadow: var(--shadow-md); 48 | transition: var(--transition-normal); 49 | max-width: 1200px; 50 | margin: 0 auto; 51 | } 52 | 53 | /* 页面标题样式 */ 54 | .page-title { 55 | color: var(--text-primary); 56 | margin-bottom: var(--spacing-lg); 57 | font-weight: 600; 58 | position: relative; 59 | padding-bottom: var(--spacing-md); 60 | } 61 | 62 | .page-title::after { 63 | content: ''; 64 | position: absolute; 65 | bottom: 0; 66 | left: 0; 67 | width: 60px; 68 | height: 3px; 69 | background-image: var(--primary-gradient); 70 | border-radius: 3px; 71 | } 72 | 73 | .page-description { 74 | color: var(--text-secondary); 75 | margin-bottom: var(--spacing-lg); 76 | font-size: 1rem; 77 | line-height: 1.6; 78 | } 79 | 80 | /* 卡片容器 */ 81 | .card-container { 82 | display: flex; 83 | flex-direction: column; 84 | gap: var(--spacing-sm); 85 | } 86 | 87 | @media (min-width: 768px) { 88 | .card-container { 89 | flex-direction: row; 90 | } 91 | } 92 | 93 | .card { 94 | background-color: var(--bg-white); 95 | border-radius: var(--border-radius-md); 96 | border: 1px solid var(--border-color); 97 | padding: var(--spacing-lg); 98 | transition: var(--transition-normal); 99 | } 100 | 101 | .card:hover { 102 | box-shadow: var(--shadow-md); 103 | transform: translateY(-2px); 104 | } 105 | 106 | /* 打印内容区域 */ 107 | .print-content { 108 | border-radius: var(--border-radius-md); 109 | border: 1px solid var(--border-color); 110 | padding: var(--spacing-md); 111 | margin-bottom: var(--spacing-lg); 112 | background-color: var(--bg-light); 113 | transition: var(--transition-normal); 114 | } 115 | 116 | /* 按钮组 */ 117 | .buttons-group { 118 | display: flex; 119 | gap: var(--spacing-md); 120 | margin-top: var(--spacing-lg); 121 | flex-wrap: wrap; 122 | } 123 | 124 | /* 按钮样式 */ 125 | .print-btn { 126 | display: flex; 127 | align-items: center; 128 | justify-content: center; 129 | padding: 10px 20px; 130 | border-radius: var(--border-radius-md); 131 | font-weight: 500; 132 | transition: var(--transition-normal); 133 | border: 1px solid var(--border-color); 134 | background-color: var(--bg-light); 135 | color: var(--text-primary); 136 | min-width: 120px; 137 | cursor: pointer; 138 | } 139 | 140 | .print-btn:hover { 141 | transform: translateY(-2px); 142 | box-shadow: var(--shadow-sm); 143 | } 144 | 145 | .print-btn.primary { 146 | background-image: var(--primary-gradient); 147 | color: white; 148 | border: none; 149 | } 150 | 151 | .print-btn.secondary { 152 | background-image: var(--secondary-gradient); 153 | color: white; 154 | border: none; 155 | } 156 | 157 | .print-btn.accent { 158 | background-image: var(--accent-gradient); 159 | color: white; 160 | border: none; 161 | } 162 | 163 | .print-btn.success { 164 | background-image: var(--success-gradient); 165 | color: white; 166 | border: none; 167 | } 168 | 169 | .print-btn.warning { 170 | background-image: var(--warning-gradient); 171 | color: white; 172 | border: none; 173 | } 174 | 175 | .print-btn.danger { 176 | background-image: var(--danger-gradient); 177 | color: white; 178 | border: none; 179 | } 180 | 181 | .print-btn:disabled { 182 | opacity: 0.7; 183 | cursor: not-allowed; 184 | transform: none; 185 | box-shadow: none; 186 | } 187 | 188 | .btn-icon { 189 | margin-right: var(--spacing-sm); 190 | font-size: 1.1rem; 191 | } 192 | 193 | .btn-icon.loading { 194 | animation: spin 1.5s infinite linear; 195 | } 196 | 197 | @keyframes spin { 198 | from { transform: rotate(0deg); } 199 | to { transform: rotate(360deg); } 200 | } 201 | 202 | /* 表格样式 */ 203 | .data-table { 204 | width: 100%; 205 | border-collapse: collapse; 206 | border: 1px solid var(--border-color); 207 | font-size: 0.95rem; 208 | box-shadow: var(--shadow-sm); 209 | overflow: hidden; 210 | border-radius: var(--border-radius-sm); 211 | } 212 | 213 | .data-table th { 214 | background-color: #f0f0f0; 215 | color: var(--text-primary); 216 | font-weight: 600; 217 | padding: 12px 8px; 218 | border-bottom: 2px solid var(--border-color); 219 | transition: background-color var(--transition-fast); 220 | } 221 | 222 | .data-table td { 223 | padding: 10px 8px; 224 | border-bottom: 1px solid var(--border-color); 225 | transition: background-color var(--transition-fast); 226 | } 227 | 228 | .data-table tbody tr:hover { 229 | background-color: rgba(58, 123, 213, 0.05); 230 | } 231 | 232 | .data-table .row-even { 233 | background-color: var(--bg-light); 234 | } 235 | 236 | /* 表单样式 */ 237 | .form-item { 238 | display: flex; 239 | align-items: center; 240 | margin-bottom: var(--spacing-md); 241 | } 242 | 243 | label { 244 | display: inline-block; 245 | text-align: left; 246 | margin-right: var(--spacing-md); 247 | font-weight: 500; 248 | color: var(--text-secondary); 249 | } 250 | 251 | input, select, textarea { 252 | padding: 8px 12px; 253 | border: 1px solid var(--border-color); 254 | border-radius: var(--border-radius-sm); 255 | transition: var(--transition-fast); 256 | width: 100%; 257 | } 258 | 259 | input:focus, select:focus, textarea:focus { 260 | outline: none; 261 | border-color: var(--primary-color); 262 | box-shadow: 0 0 0 2px rgba(58, 123, 213, 0.2); 263 | } 264 | 265 | textarea { 266 | min-height: 100px; 267 | resize: vertical; 268 | } 269 | 270 | /* 提示文本 */ 271 | .help-text { 272 | background-color: rgba(242, 201, 76, 0.1); 273 | border-left: 4px solid var(--warning-color); 274 | padding: var(--spacing-md); 275 | margin: var(--spacing-md) 0; 276 | border-radius: var(--border-radius-sm); 277 | display: flex; 278 | align-items: center; 279 | font-size: 0.9rem; 280 | color: var(--text-secondary); 281 | } 282 | 283 | .tip-icon { 284 | margin-right: var(--spacing-sm); 285 | font-size: 1.2rem; 286 | color: var(--warning-color); 287 | } 288 | 289 | /* 动画效果 */ 290 | .fade-in { 291 | animation: fadeIn var(--transition-slow) ease-in-out; 292 | } 293 | 294 | @keyframes fadeIn { 295 | from { opacity: 0; transform: translateY(10px); } 296 | to { opacity: 1; transform: translateY(0); } 297 | } 298 | 299 | /* 响应式设计 */ 300 | @media (max-width: 768px) { 301 | .buttons-group { 302 | flex-direction: column; 303 | align-items: stretch; 304 | } 305 | 306 | .print-btn { 307 | width: 100%; 308 | margin-bottom: var(--spacing-sm); 309 | } 310 | 311 | .form-item { 312 | flex-direction: column; 313 | align-items: flex-start; 314 | } 315 | 316 | label { 317 | width: 100%; 318 | text-align: left; 319 | margin-bottom: var(--spacing-xs); 320 | } 321 | 322 | .card-container { 323 | flex-direction: column; 324 | } 325 | } 326 | 327 | /* 工具类 */ 328 | .mt-xs { margin-top: var(--spacing-xs); } 329 | .mt-sm { margin-top: var(--spacing-sm); } 330 | .mt-md { margin-top: var(--spacing-md); } 331 | .mt-lg { margin-top: var(--spacing-lg); } 332 | .mt-xl { margin-top: var(--spacing-xl); } 333 | 334 | .mb-xs { margin-bottom: var(--spacing-xs); } 335 | .mb-sm { margin-bottom: var(--spacing-sm); } 336 | .mb-md { margin-bottom: var(--spacing-md); } 337 | .mb-lg { margin-bottom: var(--spacing-lg); } 338 | .mb-xl { margin-bottom: var(--spacing-xl); } 339 | 340 | .text-center { text-align: center; } 341 | .text-left { text-align: left; } 342 | .text-right { text-align: right; } 343 | 344 | .d-flex { display: flex; } 345 | .flex-row { flex-direction: row; } 346 | .flex-column { flex-direction: column; } 347 | .justify-center { justify-content: center; } 348 | .align-center { align-items: center; } 349 | .flex-wrap { flex-wrap: wrap; } 350 | .flex-grow { flex-grow: 1; } -------------------------------------------------------------------------------- /demos/vue3-demo/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/components/PrintPageLayout.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 66 | 67 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/main.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zi.yang 3 | * @Date: 2024-06-28 16:57:54 4 | * @LastEditors: zi.yang 5 | * @LastEditTime: 2025-05-09 17:02:21 6 | * @Description: 7 | * @FilePath: /vue-print-next/demos/vue3-demo/src/main.ts 8 | */ 9 | import './style.css'; 10 | import './assets/shared-styles.css'; 11 | import './assets/styles/common.css'; 12 | 13 | import { createApp } from 'vue'; 14 | 15 | import App from './App.vue'; 16 | import router from './router'; 17 | 18 | // 测试注册全局指令 19 | // import {printPlugin} from "vue-print-next"; 20 | 21 | const app= createApp(App) 22 | 23 | // app.use(printPlugin) 24 | app.use(router); 25 | app.mount('#app') 26 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/router/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zi.yang 3 | * @Date: 2024-11-08 10:03:44 4 | * @LastEditors: zi.yang 5 | * @LastEditTime: 2025-05-08 23:03:23 6 | * @Description: 7 | * @FilePath: /vue-print-next/demos/vue3-demo/src/router/index.ts 8 | */ 9 | import { createRouter, createWebHashHistory } from 'vue-router'; 10 | 11 | import CustomPreviewTools from '../views/CustomPreviewTools.vue'; 12 | import PrintAdvanced from '../views/PrintAdvanced.vue'; 13 | import PrintAsyncUrl from '../views/PrintAsyncUrl.vue'; 14 | import PrintBaiduMap from '../views/PrintBaiduMap.vue'; 15 | import PrintBasic from '../views/PrintBasic.vue'; 16 | import PrintBasic2 from '../views/PrintBasicTwo.vue'; 17 | import PrintCallbacks from '../views/PrintCallbacks.vue'; 18 | import PrintCanvas from '../views/PrintCanvas.vue'; 19 | import PrintForm from '../views/PrintForm.vue'; 20 | import PrintIframe from '../views/PrintIframe.vue'; 21 | import PrintPaperSizes from '../views/PrintPaperSizes.vue'; 22 | import PrintPdf from '../views/PrintPdf.vue'; 23 | import PrintPreviewSize from '../views/PrintPreviewSize.vue'; 24 | import PrintTable from '../views/PrintTable.vue'; 25 | import PrintUrl from '../views/PrintUrl.vue'; 26 | 27 | export const routes = [ 28 | {path: '/', component: PrintBasic, meta: {title:'基本打印用例'}}, 29 | {path: '/print-basic2', component: PrintBasic2, meta: {title:'基本打印用例2'}}, 30 | {path: '/print-table', component: PrintTable, meta: {title:'打印表格示例'}}, 31 | {path: '/print-canvas', component: PrintCanvas, meta: {title:'Canvas 打印示例'}}, 32 | {path: '/print-pdf', component: PrintPdf, meta: {title:'PDF 打印示例'}}, 33 | {path: '/print-url', component: PrintUrl, meta: {title:'Url 打印示例'}}, 34 | {path: '/print-async-url', component: PrintAsyncUrl, meta: {title:'异步 URL 打印示例'}}, 35 | {path: '/print-form', component: PrintForm, meta: {title:'Form 表单打印示例'}}, 36 | {path: '/print-iframe', component: PrintIframe, meta: {title:'Iframe 嵌套打印示例'}}, 37 | {path: '/print-baidu-map', component: PrintBaiduMap, meta: {title:'百度地图打印示例'}}, 38 | {path: '/print-advanced', component: PrintAdvanced, meta: {title:'高级打印功能示例'}}, 39 | {path: '/print-paper-sizes', component: PrintPaperSizes, meta: {title:'纸张尺寸打印示例'}}, 40 | {path: '/print-callbacks', component: PrintCallbacks, meta: {title:'打印回调函数示例'}}, 41 | {path: '/print-preview-size', component: PrintPreviewSize, meta: {title:'预览尺寸设置示例'}}, 42 | {path: '/custom-preview-tools', component: CustomPreviewTools, meta: {title:'自定义预览工具栏示例'}}, 43 | ] 44 | 45 | const router = createRouter({ 46 | history: createWebHashHistory(), 47 | routes, 48 | }) 49 | 50 | export default router 51 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | font-synthesis: none; 7 | text-rendering: optimizeLegibility; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | a { 13 | font-weight: 500; 14 | color: #646cff; 15 | text-decoration: inherit; 16 | } 17 | a:hover { 18 | color: #747bff; 19 | } 20 | 21 | html, body { 22 | height: 100%; 23 | width: 100%; 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | h1 { 29 | font-size: 3.2em; 30 | line-height: 1.1; 31 | } 32 | 33 | button { 34 | border-radius: 8px; 35 | border: 1px solid transparent; 36 | padding: 0.6em 1.2em; 37 | font-size: 1em; 38 | font-weight: 500; 39 | font-family: inherit; 40 | background-color: #f9f9f9; 41 | cursor: pointer; 42 | transition: border-color 0.25s; 43 | } 44 | 45 | button:hover { 46 | border-color: #646cff; 47 | } 48 | button:focus, 49 | button:focus-visible { 50 | outline: 4px auto -webkit-focus-ring-color; 51 | } 52 | 53 | .card { 54 | padding: 2em; 55 | } 56 | 57 | * { 58 | box-sizing: border-box; 59 | } 60 | 61 | #app { 62 | margin: 0 auto; 63 | height: 100%; 64 | width: 100%; 65 | } 66 | 67 | .mt-m { 68 | margin-top: 1rem; 69 | } 70 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/utils/common.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zi.yang 3 | * @Date: 2025-05-09 19:48:03 4 | * @LastEditors: zi.yang 5 | * @LastEditTime: 2025-05-09 19:48:25 6 | * @Description: 7 | * @FilePath: /vue-print-next/demos/vue3-demo/src/utils/common.ts 8 | */ 9 | // 可用的纸张尺寸 10 | export const paperSizes = [ 11 | { value: 'A0', label: 'A0 (841mm × 1189mm)' }, 12 | { value: 'A1', label: 'A1 (594mm × 841mm)' }, 13 | { value: 'A2', label: 'A2 (420mm × 594mm)' }, 14 | { value: 'A3', label: 'A3 (297mm × 420mm)' }, 15 | { value: 'A4', label: 'A4 (210mm × 297mm)' }, 16 | { value: 'A5', label: 'A5 (148mm × 210mm)' }, 17 | { value: 'A6', label: 'A6 (105mm × 148mm)' }, 18 | { value: 'A7', label: 'A7 (74mm × 105mm)' }, 19 | { value: 'A8', label: 'A8 (52mm × 74mm)' }, 20 | { value: 'Letter', label: 'Letter (215.9mm × 279.4mm)' }, 21 | { value: 'Legal', label: 'Legal (215.9mm × 355.6mm)' }, 22 | { value: 'Tabloid', label: 'Tabloid (279.4mm × 431.8mm)' }, 23 | { value: 'custom', label: '自定义尺寸' }, 24 | ]; -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/CustomPreviewTools.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 168 | 169 | 215 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintAdvanced.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 264 | 265 | 290 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintAsyncUrl.vue: -------------------------------------------------------------------------------- 1 | 9 | 34 | 35 | 71 | 72 | 109 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintBaiduMap.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 56 | 57 | 65 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintBasic.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 85 | 86 | 185 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintBasicTwo.vue: -------------------------------------------------------------------------------- 1 | 9 | 14 | 44 | 45 | 85 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintCallbacks.vue: -------------------------------------------------------------------------------- 1 | 92 | 93 | 164 | 165 | 305 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintCanvas.vue: -------------------------------------------------------------------------------- 1 | 198 | 199 | 226 | 227 | 280 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintForm.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 98 | 99 | 233 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintIframe.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 31 | 32 | 40 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintPaperSizes.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 248 | 249 | 280 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintPdf.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 29 | 30 | 60 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintPreviewSize.vue: -------------------------------------------------------------------------------- 1 | 76 | 77 | 298 | 299 | 455 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintTable.vue: -------------------------------------------------------------------------------- 1 | 9 | 77 | 78 | 156 | 157 | 283 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/views/PrintUrl.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 78 | 79 | 116 | -------------------------------------------------------------------------------- /demos/vue3-demo/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /demos/vue3-demo/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 5 | "target": "ES2020", 6 | "useDefineForClassFields": true, 7 | "module": "ESNext", 8 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 9 | "skipLibCheck": true, 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "moduleDetection": "force", 17 | "noEmit": true, 18 | "jsx": "preserve", 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true 25 | }, 26 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] 27 | } 28 | -------------------------------------------------------------------------------- /demos/vue3-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /demos/vue3-demo/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noEmit": true 11 | }, 12 | "include": ["vite.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /demos/vue3-demo/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig((conf) => { 6 | const isProd = conf.mode === 'production'; 7 | return { 8 | base: isProd ? '/vue-print-next/vue3-demo' :'/', 9 | plugins: [vue()], 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /docs/.vitepress/config.mts: -------------------------------------------------------------------------------- 1 | import { DefaultTheme, defineConfig } from 'vitepress'; 2 | 3 | // https://vitepress.dev/reference/site-config 4 | export default defineConfig({ 5 | lang: 'zh-Hans', 6 | base: '/vue-print-next/docs', 7 | title: "Vue Print Next", 8 | description: "Vue Print Next Document", 9 | lastUpdated: true, 10 | themeConfig: { 11 | logo: '/logo.png', 12 | 13 | // https://vitepress.dev/reference/default-theme-config 14 | socialLinks: [ 15 | {icon: 'github', link: 'https://github.com/Alessandro-Pang/vue-print-next'} 16 | ], 17 | 18 | editLink: { 19 | pattern: 'https://github.com/Alessandro-Pang/vue-print-next/edit/master/docs/:path', 20 | text: '在 GitHub 上编辑此页面' 21 | }, 22 | 23 | footer: { 24 | message: '基于 MIT 许可发布', 25 | copyright: `版权所有 © 2024-${new Date().getFullYear()} zi.Yang` 26 | }, 27 | 28 | docFooter: { 29 | prev: '上一页', 30 | next: '下一页' 31 | }, 32 | 33 | outline: { 34 | label: '页面导航' 35 | }, 36 | 37 | lastUpdated: { 38 | text: '最后更新于', 39 | formatOptions: { 40 | dateStyle: 'short', 41 | timeStyle: 'medium' 42 | } 43 | }, 44 | 45 | returnToTopLabel: '回到顶部', 46 | sidebarMenuLabel: '菜单', 47 | darkModeSwitchLabel: '主题', 48 | lightModeSwitchTitle: '切换到浅色模式', 49 | darkModeSwitchTitle: '切换到深色模式', 50 | 51 | nav: [ 52 | {text: '首页', link: '/'}, 53 | {text: '快速上手', link: '/guide/what-is-vue-print-next'}, 54 | {text: '在线演示', link: 'https://alexpang.cn/vue-print-next/vue3-demo'} 55 | ], 56 | 57 | sidebar: { 58 | '/guide/': { 59 | base: '/guide/', 60 | items: sidebarGuide() 61 | }, 62 | }, 63 | 64 | search: { 65 | provider: 'local', 66 | options: { 67 | placeholder: '搜索文档', 68 | translations: { 69 | button: { 70 | buttonText: '搜索文档', 71 | buttonAriaLabel: '搜索文档' 72 | }, 73 | modal: { 74 | searchBox: { 75 | resetButtonTitle: '清除查询条件', 76 | resetButtonAriaLabel: '清除查询条件', 77 | cancelButtonText: '取消', 78 | cancelButtonAriaLabel: '取消' 79 | }, 80 | startScreen: { 81 | recentSearchesTitle: '搜索历史', 82 | noRecentSearchesText: '没有搜索历史', 83 | saveRecentSearchButtonTitle: '保存至搜索历史', 84 | removeRecentSearchButtonTitle: '从搜索历史中移除', 85 | favoriteSearchesTitle: '收藏', 86 | removeFavoriteSearchButtonTitle: '从收藏中移除' 87 | }, 88 | errorScreen: { 89 | titleText: '无法获取结果', 90 | helpText: '你可能需要检查你的网络连接' 91 | }, 92 | footer: { 93 | selectText: '选择', 94 | navigateText: '切换', 95 | closeText: '关闭', 96 | searchByText: '搜索提供者' 97 | }, 98 | noResultsScreen: { 99 | noResultsText: '无法找到相关结果', 100 | suggestedQueryText: '你可以尝试查询', 101 | reportMissingResultsText: '你认为该查询应该有结果?', 102 | reportMissingResultsLinkText: '点击反馈' 103 | } 104 | } 105 | } 106 | } 107 | } 108 | }, 109 | }) 110 | 111 | function sidebarGuide(): DefaultTheme.SidebarItem[] { 112 | return [ 113 | { 114 | text: '简介', 115 | collapsed: false, 116 | items: [ 117 | {text: '什么是 VuePrintNext?', link: 'what-is-vue-print-next'}, 118 | {text: '快速开始', link: 'getting-started'}, 119 | ] 120 | }, 121 | { 122 | text: '高级', 123 | collapsed: false, 124 | items: [ 125 | {text: '生命周期', link: 'life-cycle'}, 126 | {text: '预览工具', link: 'preview-tools'}, 127 | {text: '实现原理', link: 'impl-principle'}, 128 | ] 129 | }, 130 | {text: 'API 参考', link: 'api'}, 131 | {text: '常见问题', link: 'faq'}, 132 | {text: '从 vue-print-nb 迁移', link: 'migration-from-vue-print-nb'}, 133 | ] 134 | } 135 | 136 | -------------------------------------------------------------------------------- /docs/guide/api.md: -------------------------------------------------------------------------------- 1 | # API 参数详解 2 | 3 | ## `el` 4 | 5 | - **类型**: `string` \| `HtmlElement` 6 | - **说明**: 指定需要打印的元素。可以是 CSS 选择器(如 `#elementId`)或实际的 DOM 元素节点(如 `document.getElementById('elementId')`)。 7 | - **默认值**: 无 8 | - **示例**: 9 | 10 | ```ts twoslash 11 | // 使用 CSS 选择器 12 | new VuePrintNext({ el: '#printMe' }); 13 | 14 | // 使用 DOM 节点 15 | const element = document.getElementById('printMe'); 16 | new VuePrintNext({ el: element }); 17 | ``` 18 | 19 | ## `standard` 20 | 21 | - **类型**: `string` 22 | - **说明**: 指定打印的文档类型。可以设置为 `html5`、`loose` 或 `strict`。默认使用 `html5`。 23 | - **默认值**: `html5` 24 | - **示例**: 25 | 26 | ```typescript 27 | new VuePrintNext({ 28 | el: '#printMe', 29 | standard: 'strict' // 使用严格模式 30 | }); 31 | ``` 32 | 33 | ## `noPrintSelector` 34 | 35 | - **类型**: `string[]` \| `string` 36 | - **说明**: 指定在打印时需要忽略的元素。可以传入一个 CSS 选择器字符串或一个字符串数组。如果传入数组,数组中的所有选择器都会被忽略。 37 | - **默认值**: 无 38 | - **示例**: 39 | 40 | ```typescript 41 | new VuePrintNext({ 42 | el: '#printMe', 43 | noPrintSelector: '.no-print' // 忽略具有 no-print 类的元素 44 | }); 45 | 46 | // 忽略多个选择器 47 | new VuePrintNext({ 48 | el: '#printMe', 49 | noPrintSelector: ['.no-print', '.ignore-me'] 50 | }); 51 | ``` 52 | 53 | ## `popTitle` 54 | 55 | - **类型**: `string` 56 | - **说明**: 设置打印窗口的页眉标题。如果未设置,将使用当前页面的 `` 标签内容作为页眉。 57 | - **默认值**: 当前页面的 `<title>` 标签内容 58 | - **示例**: 59 | 60 | ```typescript 61 | new VuePrintNext({ 62 | el: '#printMe', 63 | popTitle: '打印标题' // 自定义标题 64 | }); 65 | ``` 66 | 67 | ## `preview` 68 | 69 | - **类型**: `boolean` 70 | - **说明**: 是否启用打印预览功能。启用后,打印前会显示一个预览窗口。 71 | - **默认值**: `false` 72 | - **示例**: 73 | 74 | ```typescript 75 | new VuePrintNext({ 76 | el: '#printMe', 77 | preview: true // 启用打印预览 78 | }); 79 | ``` 80 | 81 | ## `previewTitle` 82 | 83 | - **类型**: `string` 84 | - **说明**: 设置预览窗口的标题。 85 | - **默认值**: `'打印预览'` 86 | - **示例**: 87 | 88 | ```typescript 89 | new VuePrintNext({ 90 | el: '#printMe', 91 | preview: true, 92 | previewTitle: '预览打印' // 自定义预览标题 93 | }); 94 | ``` 95 | 96 | ## `previewPrintBtnLabel` 97 | 98 | - **类型**: `string` 99 | - **说明**: 设置预览窗口中的打印按钮标签。 100 | - **默认值**: `'打印'` 101 | - **示例**: 102 | 103 | ```typescript 104 | new VuePrintNext({ 105 | el: '#printMe', 106 | preview: true, 107 | previewPrintBtnLabel: '开始打印' // 自定义打印按钮标签 108 | }); 109 | ``` 110 | 111 | ## `extraCss` 112 | 113 | - **类型**: `string` 114 | - **说明**: 指定额外的 CSS 文件路径,这些 CSS 文件会被应用到打印内容中。 115 | - **默认值**: 无 116 | - **示例**: 117 | 118 | ```typescript 119 | new VuePrintNext({ 120 | el: '#printMe', 121 | extraCss: 'https://cdn.example.com/extra.css' // 添加额外的 CSS 122 | }); 123 | ``` 124 | 125 | ## `extraHead` 126 | 127 | - **类型**: `string` 128 | - **说明**: 指定额外的 `<head>` 内容,例如自定义的 `<meta>` 标签或其他 `<link>` 标签。 129 | - **默认值**: 无 130 | - **示例**: 131 | 132 | ```typescript 133 | new VuePrintNext({ 134 | el: '#printMe', 135 | extraHead: '<meta name="viewport" content="width=device-width, initial-scale=1.0">' // 添加额外的 <head> 内容 136 | }); 137 | ``` 138 | 139 | ## `url` 140 | 141 | - **类型**: `string` 142 | - **说明**: 指定要打印的网址内容。必须保证该 URL 符合同源策略。 143 | - **默认值**: 无 144 | - **示例**: 145 | 146 | ```typescript 147 | new VuePrintNext({ 148 | url: 'https://example.com/print-content' // 打印指定 URL 内容 149 | }); 150 | ``` 151 | 152 | ## `asyncUrl` 153 | 154 | - **类型**: `function` 155 | - **说明**: 一个函数,用于异步加载 URL 内容。函数接受一个 `resolve` 回调,用于提供异步加载完成后的 URL。 156 | - **默认值**: 无 157 | - **示例**: 158 | 159 | ```typescript 160 | new VuePrintNext({ 161 | asyncUrl(resolve) { 162 | setTimeout(() => { 163 | resolve('https://example.com/print-content'); // 异步加载 URL 164 | }, 2000); 165 | } 166 | }); 167 | ``` 168 | 169 | ## `zIndex` 170 | 171 | - **类型**: `number` 172 | - **说明**: 设置预览窗口的 `z-index` 值,以确保它在其他元素之上。 173 | - **默认值**: `20002` 174 | - **示例**: 175 | 176 | ```typescript 177 | new VuePrintNext({ 178 | el: '#printMe', 179 | zIndex: 30000 // 设置预览窗口的 z-index 180 | }); 181 | ``` 182 | 183 | ## `openCallback` 184 | 185 | - **类型**: `function` 186 | - **说明**: 打印窗口打开时的回调函数。 187 | - **默认值**: 无 188 | - **示例**: 189 | 190 | ```typescript 191 | new VuePrintNext({ 192 | el: '#printMe', 193 | openCallback() { 194 | console.log('打印窗口已打开'); 195 | } 196 | }); 197 | ``` 198 | 199 | ## `closeCallback` 200 | 201 | - **类型**: `function` 202 | - **说明**: 打印窗口关闭时的回调函数,无论用户确认还是取消打印,都会触发此回调。 203 | - **默认值**: 无 204 | - **示例**: 205 | 206 | ```typescript 207 | new VuePrintNext({ 208 | el: '#printMe', 209 | closeCallback() { 210 | console.log('打印窗口已关闭'); 211 | } 212 | }); 213 | ``` 214 | 215 | ## `beforeOpenCallback` 216 | 217 | - **类型**: `function` 218 | - **说明**: 打印窗口打开前的回调函数,仅在打印预览使用时有效。 219 | - **默认值**: 无 220 | - **示例**: 221 | 222 | ```typescript 223 | new VuePrintNext({ 224 | el: '#printMe', 225 | preview: true, 226 | beforeOpenCallback() { 227 | console.log('打印预览窗口即将打开'); 228 | } 229 | }); 230 | ``` 231 | 232 | ## `previewBeforeOpenCallback` 233 | 234 | - **类型**: `function` 235 | - **说明**: 预览框架 `iframe` 加载前的回调函数,仅在打印预览使用时有效。 236 | - **默认值**: 无 237 | - **示例**: 238 | 239 | ```typescript 240 | new VuePrintNext({ 241 | el: '#printMe', 242 | preview: true, 243 | previewBeforeOpenCallback() { 244 | console.log('预览框架 iframe 加载前'); 245 | } 246 | }); 247 | ``` 248 | 249 | ## `previewOpenCallback` 250 | 251 | - **类型**: `function` 252 | - **说明**: 预览框架 `iframe` 加载完成后的回调函数,仅在打印预览使用时有效。 253 | - **默认值**: 无 254 | - **示例**: 255 | 256 | ```typescript 257 | new VuePrintNext({ 258 | el: '#printMe', 259 | preview: true, 260 | previewOpenCallback() { 261 | console.log('预览框架 iframe 加载完成'); 262 | } 263 | }); 264 | ``` 265 | 266 | ## `paperSize` 267 | 268 | - **类型**: `string` 269 | - **说明**: 设置打印纸张的尺寸。可选值包括 'A0'、'A1'、'A2'、'A3'、'A4'、'A5'、'A6'、'A7'、'A8'、'Letter'、'Legal'、'Tabloid' 和 'custom'。当设置为 'custom' 时,需要配合 `customSize` 参数使用。 270 | - **默认值**: `'A4'` 271 | - **示例**: 272 | 273 | ```typescript 274 | new VuePrintNext({ 275 | el: '#printMe', 276 | paperSize: 'A4' // 设置纸张尺寸为 A4 277 | }); 278 | ``` 279 | 280 | ## `orientation` 281 | 282 | - **类型**: `string` 283 | - **说明**: 设置打印纸张的方向。可选值为 'portrait'(纵向)或 'landscape'(横向)。 284 | - **默认值**: `'portrait'` 285 | - **示例**: 286 | 287 | ```typescript 288 | new VuePrintNext({ 289 | el: '#printMe', 290 | orientation: 'landscape' // 设置纸张方向为横向 291 | }); 292 | ``` 293 | 294 | ## `customSize` 295 | 296 | - **类型**: `object` 297 | - **说明**: 设置自定义纸张尺寸,仅当 `paperSize` 设置为 'custom' 时生效。对象包含以下属性: 298 | - `width`: 宽度值(字符串) 299 | - `height`: 高度值(字符串) 300 | - `unit`: 单位,可选值为 'mm'、'cm'、'in' 或 'px'(可选,默认为 'mm') 301 | - **默认值**: 无 302 | - **示例**: 303 | 304 | ```typescript 305 | new VuePrintNext({ 306 | el: '#printMe', 307 | paperSize: 'custom', 308 | customSize: { 309 | width: '100', 310 | height: '150', 311 | unit: 'mm' 312 | } 313 | }); 314 | ``` 315 | 316 | ## `darkMode` 317 | 318 | - **类型**: `boolean` 319 | - **说明**: 是否默认使用深色模式显示预览界面。 320 | - **默认值**: `false` 321 | - **示例**: 322 | 323 | ```typescript 324 | new VuePrintNext({ 325 | el: '#printMe', 326 | preview: true, 327 | darkMode: true // 启用深色模式 328 | }); 329 | ``` 330 | 331 | ## `windowMode` 332 | 333 | - **类型**: `boolean` 334 | - **说明**: 是否默认使用弹窗模式(非全屏)显示预览界面。 335 | - **默认值**: `false` 336 | - **示例**: 337 | 338 | ```typescript 339 | new VuePrintNext({ 340 | el: '#printMe', 341 | preview: true, 342 | windowMode: true // 使用弹窗模式 343 | }); 344 | ``` 345 | 346 | ## `defaultScale` 347 | 348 | - **类型**: `number` 349 | - **说明**: 设置预览界面的默认缩放比例。 350 | - **默认值**: `1` 351 | - **示例**: 352 | 353 | ```typescript 354 | new VuePrintNext({ 355 | el: '#printMe', 356 | preview: true, 357 | defaultScale: 0.8 // 设置默认缩放比例为 80% 358 | }); 359 | ``` 360 | -------------------------------------------------------------------------------- /docs/guide/faq.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | ## 1. 插件、方法支持 Vue2 吗? 4 | 5 | **Q:** 该插件是否兼容 Vue2? 6 | 7 | **A:** 是的,`VuePrintNext` 插件对 Vue2 的指令插件机制做了兼容,因此可以在 Vue2 环境下使用。然而,由于插件主要为 Vue3 设计,可能会存在与 Vue2 的兼容性问题,特别是在处理 IE 浏览器时。如果你需要更广泛的兼容性,建议使用 [vue-print-nb](https://github.com/Power-kxLee/vue2-print-nb) 插件。 8 | 9 | ## 2. `VuePrintNext` 必须传入哪些参数? 10 | 11 | **Q:** `VuePrintNext` 类必须传入 `el`、`url`、`asyncUrl` 其中一个参数,但不支持全屏打印? 12 | 13 | **A:** `v-print` 指令允许不传入任何参数,此时会打印整个页面。如果使用 `VuePrintNext` 类进行全屏打印,请将 `el` 参数设置为 `'body'`。确保至少传入 `el`、`url` 或 `asyncUrl` 中的一个参数,以便正常使用插件。 14 | 15 | ## 3. 打印窗口的确认和取消按钮的回调如何处理? 16 | 17 | **Q:** 如何监听打印窗口的确认和取消按钮的点击事件? 18 | 19 | **A:** 打印窗口的确认和取消按钮是由浏览器提供的,无法直接在 `VuePrintNext` 类中监听。因此,`closeCallback` 回调函数将在打印窗口关闭时触发,不论用户是否确认或取消打印。 20 | 21 | ## 4. 其他框架或无框架环境下如何使用? 22 | 23 | **Q:** `VuePrintNext` 是否可以在其他框架或无框架环境下使用? 24 | 25 | **A:** `VuePrintNext` 是一个纯 JavaScript 实现的类,理论上与框架无关,因此可以在其他框架或无框架环境中使用。用户只需通过调用 `VuePrintNext` 类即可。在其他框架中,指令和插件模式不可用,只能通过 API 调用。 26 | 27 | ## 5. 打印时出现样式问题? 28 | 29 | **Q:** 打印出来的内容样式与预期不符,可能是什么原因? 30 | 31 | **A:** 样式问题可能由以下几种原因造成: 32 | - **公共样式影响**: 33 | - 例如 `body` 设置为 `flex` 会影响打印内容的宽度。请确保打印内容的容器具有固定宽度。 34 | - **`overflow: hidden`**:如果 `html` 或 `body` 设置为 `hidden`,可能会导致打印时无法完全展示内容。 35 | - `font-size: 0`, `opacity: 0`: 如果全局样式中存在以上类似的样式,可能会导致打印时问题、元素展示不出来。 36 | - **CSS 动画**:如果页面上有 CSS 动画,可能会导致打印内容与实际展示不符。使用 `preview: true` 打开预览窗口,并等待动画完成后再进行打印。 37 | 38 | ## 6. 如何处理 URL 跨域问题? 39 | 40 | **Q:** 打印跨域 URL 内容时遇到问题,该如何处理? 41 | 42 | **A:** 由于浏览器的安全策略,`iframe` 无法对跨域站点触发 `print` 方法。可以通过在页面中内嵌一个 `iframe` 加载跨域页面,然后将 `el` 设置为 `'iframe'` 来解决这个问题。请确保 URL 符合同源策略或使用代理服务解决跨域问题。 43 | 44 | ## 7. 打印内容与实际内容不一致怎么办? 45 | 46 | **Q:** 打印内容与预期结果不一致,如何检查? 47 | 48 | **A:** 使用 `preview: true` 打开打印预览窗口,检查和调整打印内容。预览窗口中的样式即为实际打印的样式,这可以帮助你在打印前发现和修正问题。 49 | 50 | ## 8. 打印预览窗口无法正确加载? 51 | 52 | **Q:** 打印预览窗口无法正确加载或显示不全? 53 | 54 | **A:** 确保传入的 `extraCss` 和 `extraHead` 参数中的路径和内容正确。如果预览窗口仍然无法加载,请检查网络请求是否成功,或尝试在本地调试和修复问题。 55 | 56 | ## 9. 如何调试打印功能? 57 | 58 | **Q:** 在使用 `VuePrintNext` 时遇到调试困难,如何有效调试? 59 | 60 | **A:** 使用浏览器的开发者工具检查打印内容的 HTML 和 CSS。通过 `console.log` 输出调试信息,查看回调函数是否正常触发。确保使用的参数正确并且符合预期。 61 | 62 | ## 10. 为什么打印 地图、Three.js 等 WebGL 时是空白的 63 | 64 | **Q:** 在打印 `canvas` 时,`canvas` 采用了 `webgl` 绘制 3D 图形(如 GIS 地图,Three.js 等),会导致打印出来的是空白的。 65 | 66 | **A:** 这和 webgl 的一个上下文参数有关:`preserveDrawingBuffer`, 这个值默认为 `false`, 67 | 这个参数是浏览器为了性能和兼容性,当完成绘制后,浏览器默认会清除 WebGL 画布的绘制缓存。 68 | 这就导致了打印时调用 `canvas.toDataURL` 获取到的图片是一片空白。 69 | 70 | **解决办法:** 在执行初始化 3D 图形之前,重写 `getContext` 方法,使绘制 `webgl` 时 `preserveDrawingBuffer` 参数为 `true` 71 | 72 | ```js 73 | // 重写 getContext 方法 74 | HTMLCanvasElement.prototype.getContext = (function (origFn) { 75 | return function (type, attributes) { 76 | if (type === 'webgl') { 77 | attributes = Object.assign({}, attributes, { 78 | preserveDrawingBuffer: true, 79 | }); 80 | } 81 | return origFn.call(this, type, attributes); 82 | }; 83 | })(HTMLCanvasElement.prototype.getContext); 84 | ``` 85 | 86 | --- 87 | 88 | 如果有其他问题或需要进一步的支持,请随时在 [GitHub Issues](https://github.com/Alessandro-Pang/vue-print-next/issues) 上提出问题和提交 Pull Request。 89 | -------------------------------------------------------------------------------- /docs/guide/getting-started.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | 要快速上手使用 `VuePrintNext`,请按照以下步骤进行集成和使用: 4 | 5 | ## 1. 安装插件 6 | 7 | 首先,安装 `VuePrintNext` 插件。你可以通过以下命令使用 `npm`、`yarn` 或 `pnpm` 安装: 8 | 9 | ```bash 10 | npm install vue-print-next --save 11 | # 或者 12 | yarn add vue-print-next 13 | # 或者 14 | pnpm add vue-print-next 15 | ``` 16 | 17 | ## 2. Vue 3 项目 18 | 19 | 1. **全局注册插件** 20 | 21 | 在你的 `main.ts` 文件中,导入并使用插件: 22 | 23 | ```typescript 24 | import { createApp } from 'vue'; 25 | import App from './App.vue'; 26 | import { printPlugin } from 'vue-print-next'; 27 | 28 | const app = createApp(App); 29 | app.use(printPlugin); 30 | app.mount('#app'); 31 | ``` 32 | 33 | 2. **在组件中使用指令** 34 | 35 | 使用 `v-print` 指令进行打印。以下是一个示例: 36 | 37 | ```vue 38 | <template> 39 | <div> 40 | <button v-print>打印整个页面</button> 41 | <button v-print="'#printMe'">打印局部内容</button> 42 | <div id="printMe"> 43 | <p>这是需要打印的局部内容</p> 44 | <p>更多内容...</p> 45 | </div> 46 | </div> 47 | </template> 48 | ``` 49 | 50 | ## 3. Vue 2 项目 51 | 52 | 1. **全局注册插件** 53 | 54 | 在你的 `main.js` 文件中,导入并使用插件: 55 | 56 | ```javascript 57 | import Vue from 'vue'; 58 | import App from './App.vue'; 59 | import { printPlugin } from 'vue-print-next'; 60 | 61 | Vue.use(printPlugin); 62 | 63 | new Vue({ 64 | render: h => h(App), 65 | }).$mount('#app'); 66 | ``` 67 | 68 | 2. **在组件中使用指令** 69 | 70 | 在组件中使用 `v-print` 指令进行打印: 71 | 72 | ```vue 73 | <template> 74 | <div> 75 | <button v-print>打印整个页面</button> 76 | <button v-print="'#printMe'">打印局部内容</button> 77 | <div id="printMe"> 78 | <p>这是需要打印的局部内容</p> 79 | <p>更多内容...</p> 80 | </div> 81 | </div> 82 | </template> 83 | 84 | <script> 85 | import { vPrint } from 'vue-print-next'; 86 | 87 | export default { 88 | name: 'App', 89 | directives: { 90 | print: vPrint 91 | }, 92 | } 93 | </script> 94 | ``` 95 | 96 | ## 4. 使用 `VuePrintNext` 类 97 | 98 | 如果需要更复杂的打印逻辑,可以直接使用 `VuePrintNext` 类: 99 | 100 | ```vue 101 | <template> 102 | <div> 103 | <button @click="handlePrint">打印局部内容</button> 104 | <div id="printMe"> 105 | <p>这是需要打印的内容</p> 106 | </div> 107 | </div> 108 | </template> 109 | 110 | <script setup> 111 | import { VuePrintNext } from 'vue-print-next'; 112 | 113 | function handlePrint() { 114 | new VuePrintNext({ el: '#printMe', /* 其他参数 */ }); 115 | } 116 | </script> 117 | ``` 118 | -------------------------------------------------------------------------------- /docs/guide/impl-principle.md: -------------------------------------------------------------------------------- 1 | # Vue3 自定义打印原理 2 | 3 | 最近接触到了一个 Vue3 的打印需求,我发现自己虽然从事前端开发已有多年,但对如何实现自定义打印还没有深入研究,一般都是找现成的库来解决问题,借这次的机会研究了一下如何实现自定义打印。 4 | 5 | 在现在的前端开发中,打印是一个比较常见的功能,无论是在生成报表、下载发票还是其他用途上都会用到。 最基本的打印方式是直接打印整个页面内容,但在实际项目中,我们常常需要更灵活的打印功能,例如选择性打印某个特定部分、提供打印预览功能等。这就产生了许多 JavaScript 打印库,如 `print-js`、`vue-print-nb` 等,这些库不仅简化了打印流程,还提供了丰富的自定义打印配置,满足各种不同的打印需求。 6 | 7 | 由于当前的项目基于 Vue3 进行开发,我简单调研后发现 `vue-print-nb` 是 Vue 中常用的打印库,同时其提供了适用于 Vue3 的 `vue3-print-nb` 库。然而,在实际使用过程中,我发现该库由于发布较早,且后期未继续维护,因此在支持 Vue3 的一些特性方面有所欠缺,所以我在此基础上进行了优化和改造,来更好地满足需求。 8 | 9 | 本文将详细探讨如何使用原生 JavaScript 实现自定义打印功能,同时讲解对 `vue3-print-nb` 的改造和优化。 10 | 11 | # 实现自定义打印 12 | 13 | 实现自定义打印的核心思想是通过将要打印的内容放入一个 `iframe` 或者新窗口中,然后调用 `window.print()` 方法进行打印。然而,需要考虑的问题远不止这些,例如样式还原、表单展示、以及 canvas 的打印等。 14 | 15 | ## 实现思路 16 | 17 | ### 1. 确定打印区域 18 | 19 | 首先,需要确定用户希望打印的页面区域, 这通常通过用户传递的一个选择器或直接传入的 DOM 元素来实现: 20 | 21 | - **选择器**:通过传递一个 CSS 选择器来标识需要打印的元素。 22 | - **DOM 元素**:直接传递所需打印的 DOM 对象。 23 | 24 | ### 2. 克隆打印内容 25 | 26 | 为了保证打印内容与页面其他部分隔离开来,通常会将需要打印的内容深度克隆。这确保了打印时不会受到动态内容变化的干扰,并且可以对其进行独立处理: 27 | 28 | ```javascript 29 | /** 30 | * 拷贝需要打印的节点 31 | */ 32 | function cloneContent(selector) { 33 | const originalContent = document.querySelector(selector).cloneNode(true); 34 | 35 | // 在克隆内容中处理样式和事件,比如删除不需要的节点 36 | originalContent.querySelectorAll('.no-print').forEach((el) => el.remove()); 37 | 38 | return originalContent.outerHTML; 39 | } 40 | ``` 41 | 42 | ### 3. 创建新的上下文 43 | 44 | 为了确保打印内容不受当前页面的影响,通常会创建一个新的窗口或 iframe 并复制打印内容到新的上下文中。两种常见的实现方式是: 45 | 46 | - **新窗口**:打开一个新的浏览器窗口或标签页,将克隆内容放置其内,然后触发打印。 47 | - **Iframe**:在当前页面中创建一个隐藏的 `iframe`,将打印内容放入 `iframe` 后,再从 `iframe` 中触发打印。 48 | 49 | ```javascript 50 | /** 51 | * 创建一个新的上下文(打印窗口) 52 | */ 53 | function createPrintIframe() { 54 | const iframe = document.createElement('iframe'); 55 | iframe.id = this.iframeId; 56 | iframe.src = new Date().getTime().toString(); 57 | iframe.style.display = 'none'; 58 | document.body.appendChild(iframe); 59 | return iframe; 60 | } 61 | 62 | /** 63 | * 将需要打印的内容写入新的上下文 64 | */ 65 | function write(printWindow, writeContent) { 66 | const iframeDocument = printWindow.contentDocument; 67 | iframeDocument.open(); 68 | iframeDocument.write( 69 | `<!DOCTYPE html><html lang='zh'><head>${getHead()}</head><body>${writeContent}</body></html>` 70 | ); 71 | iframeDocument.close(); 72 | } 73 | ``` 74 | 75 | ### 4. 处理打印样式 76 | 77 | 设计打印库时,首先需要解决的问题就是如何在新的上下文中还原样式,包括页面原有样式和用户自定义样式,以满足复杂业务需求。 78 | 79 | - **独立样式**:插入到新的打印文档或上下文中,可以是内联样式或者外部样式表。 80 | - **打印媒体查询**:使用 `@media print` 媒体查询,以定义仅在打印时生效的特殊样式,使得打印和屏幕显示效果独立。 81 | 82 | ```javascript 83 | /** 84 | * 获取 head 85 | */ 86 | function getHead() { 87 | // 获取网页原有的 css 链接 88 | const links = Array.from(document.querySelectorAll('link')) 89 | .filter((item) => item.href.includes('.css')) 90 | .map( 91 | (item) => `<link type="text/css" rel="stylesheet" href='${item.href}'>` 92 | ) 93 | .join(''); 94 | 95 | // 获取页面中的 style 标签 96 | const style = Array.from(document.styleSheets).reduce((acc, styleSheet) => { 97 | const rules = styleSheet.cssRules || styleSheet.rules; 98 | if (!rules) return acc; 99 | acc += Array.from(rules).reduce( 100 | (innerAcc, rule) => innerAcc + rule.cssText, 101 | '' 102 | ); 103 | return acc; 104 | }, ''); 105 | // ...省略处理用户自定义引入的 css 样式 106 | 107 | // 返回 head 108 | return `<title>自定义打印示例${links}`; 109 | } 110 | ``` 111 | 112 | ### 5. 触发打印 113 | 114 | 准备好打印内容和样式后,打印库通过调用 `window.print` 方法来触发打印。打印操作通常包含以下步骤: 115 | 116 | - **打印对话框**:调用 `window.print` 来触发系统打印对话框。 117 | - **清除上下文**:打印完成后,关闭或销毁用于打印的临时窗口或 `iframe`,以避免内存泄漏和资源占用。 118 | 119 | ```javascript 120 | /** 121 | * 触发打印操作 122 | */ 123 | function triggerPrint(printWindow) { 124 | const iframeWin = printWindow?.contentWindow; 125 | iframeWin.addEventListener('load', () => { 126 | iframeWin.focus(); 127 | iframeWin.print(); 128 | iframe.remove(); 129 | }); 130 | } 131 | ``` 132 | 133 | 通过上述步骤,我们就可以灵活地控制打印内容和样式,并能提供良好的打印体验和可定制化的功能。 134 | 135 | ## 综合示例 136 | 137 | 通过以上步骤,我们可以实现一个简易的自定义打印功能。 138 | 139 | ```html 140 | 141 | 142 | 143 | 144 | 145 | 146 | 自定义打印示例 147 | 167 | 168 | 169 | 170 |
171 |

葫芦娃

172 |

葫芦娃 葫芦娃

173 |

一根藤上七朵花

174 |

风吹雨打都不怕

175 |

啦啦啦啦

176 |

叮当当咚咚当当 葫芦娃

177 |

叮当当咚咚当当 本领大

178 |

啦啦啦啦

179 |

我是不需要打印的内容,啦啦啦啦啦

180 |

我是不需要打印的内容,啦啦啦啦啦

181 |

我是不需要打印的内容,啦啦啦啦啦

182 |

我是不需要打印的内容,啦啦啦啦啦

183 |
184 |
185 | 186 |
187 | 188 | 285 | 286 | 287 | ``` 288 | 289 | 实现效果如下: 290 | 291 | ![](https://blog.pangcy.cn/img/posts/5.%E5%89%8D%E7%AB%AF%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6%E4%B8%8E%E5%BA%93/Vue/attachments/Pasted%20image%2020240630140309.webp) 292 | 293 | 通过上述示例,可以实现一个简易的自定义打印功能。在实际业务中,我们可能需要支持更多复杂的需求,如外部 CSS 的引入,打印 canvas、表单等。 294 | 295 | # `vue3-print-nb` 296 | 297 | `vue3-print-nb` 是一个用于 Vue3 的打印插件,提供了相对丰富的打印功能。但在实际使用过程中,我发现了一些不足之处,例如对 Vue3 setup 函数的支持不够友好,以及不支持手动调用等。 298 | 299 | ## 优化和改造 300 | 301 | 为了更好地支持 Vue3 setup 函数,我对 `vue3-print-nb` 进行了以下改造: 302 | 303 | 1. 使用 TypeScript 重写了整个库,使得调用传参时体验更佳。 304 | 2. 增加了对 `VuePrintNext` 类的导出,允许手动调用打印方法,不再局限于指令调用。 305 | 3. 增强 Vue3 setup 支持,允许通过直接导入 `vPrint` 指令进行局部导入使用。 306 | 4. 支持设置指定的 CSS 选择器来忽略打印部分内容。 307 | 5. 支持通过 CSS 选择器或手动传入 DOM 节点进行局部打印。 308 | 309 | 此外,还修复了一些已知的 bug: 310 | 311 | - 背景色打印失效问题。 312 | - 通过 URL 打印时,有时无法触发打印行为的问题。 313 | - 其他一些小问题。 314 | 315 | ## 新打印插件 316 | 317 | 我已将上述优化和改造封装成一个新的插件:`vue-print-next`,并已上传至 GitHub:[https://github.com/Alessandro-Pang/vue-print-next](https://github.com/Alessandro-Pang/vue-print-next)。你可以通过 npm 进行安装和使用。 318 | 319 | ```sh 320 | npm install vue-print-next 321 | ``` 322 | 323 | # 结语 324 | 325 | 通过本文,我们对自定义打印的实现原理有了比较全面的了解。尽管实现打印功能仍然依赖于 `window.print` API,但通过 `window.open` 或者 `iframe` 巧妙的将所需打印内容进行隔离处理,可以实现灵活的打印功能。 326 | 327 | # 相关链接 328 | 329 | - window.print: [https://developer.mozilla.org/zh-CN/docs/Web/API/Window/print](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/print) 330 | - vue3-print-nb: [https://github.com/Power-kxLee/vue3-print-nb](https://github.com/Power-kxLee/vue3-print-nb) 331 | 332 | --- 333 | 334 | 本文出自《Vue3 自定义打印原理》 原文链接:https://blog.pangcy.cn/2024/06/28/%E5%89%8D%E7%AB%AF%E7%BC%96%E7%A8%8B%E7%9B%B8%E5%85%B3/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6%E4%B8%8E%E5%BA%93/Vue/Vue3%20%E8%87%AA%E5%AE%9A%E4%B9%89%E6%89%93%E5%8D%B0%E5%8E%9F%E7%90%86/ 335 | -------------------------------------------------------------------------------- /docs/guide/life-cycle.md: -------------------------------------------------------------------------------- 1 | # 生命周期 2 | 3 | `VuePrintNext` 提供了一系列的生命周期钩子函数,允许用户在打印过程的不同阶段执行自定义逻辑。通过这些钩子函数,你可以在打开打印窗口、关闭窗口以及打印预览的各个环节进行自定义操作。以下是生命周期钩子的详细介绍和使用示例: 4 | 5 | ## `openCallback` 6 | 7 | - **说明**:在打印窗口打开时触发的回调函数。 8 | - **用途**:可以在打印窗口打开时执行某些初始化操作或记录日志。 9 | 10 | ### 示例: 11 | 12 | ```javascript 13 | const printObj = { 14 | el: '#printMe', 15 | openCallback() { 16 | console.log('打印窗口已经打开'); 17 | } 18 | } 19 | ``` 20 | 21 | 当打印窗口打开时,控制台会输出 “打印窗口已经打开”。 22 | 23 | 24 | ## `closeCallback` 25 | 26 | - **说明**:在打印窗口关闭时触发的回调函数。 27 | - **用途**:在用户打印或取消打印后,执行清理操作或其他业务逻辑。无论用户是选择打印还是取消打印,该回调都会被触发。 28 | 29 | ### 示例: 30 | 31 | ```javascript 32 | const printObj = { 33 | el: '#printMe', 34 | closeCallback() { 35 | console.log('打印窗口已经关闭'); 36 | } 37 | } 38 | ``` 39 | 40 | 当打印窗口关闭时,控制台会输出 “打印窗口已经关闭”。 41 | 42 | 43 | ## `beforeOpenCallback` 44 | 45 | - **说明**:在打印窗口打开之前触发的回调函数,主要用于打印预览时使用。 46 | - **用途**:在打开预览窗口前执行一些准备工作,比如修改内容或调整样式。 47 | 48 | ### 示例: 49 | 50 | ```javascript 51 | const printObj = { 52 | el: '#printMe', 53 | preview: true, 54 | beforeOpenCallback() { 55 | console.log('打印预览窗口即将打开'); 56 | } 57 | } 58 | ``` 59 | 60 | 当打印预览窗口准备打开时,控制台会输出 “打印预览窗口即将打开”。 61 | 62 | ## `previewOpenCallback` 63 | 64 | - **说明**:打印预览的 iframe 加载完成后触发的回调函数。 65 | - **用途**:可以在打印预览内容加载完毕后执行进一步的操作,比如自动调整预览窗口中的内容,或者根据需要添加自定义样式。 66 | 67 | ### 示例: 68 | 69 | ```javascript 70 | const printObj = { 71 | el: '#printMe', 72 | preview: true, 73 | previewOpenCallback() { 74 | console.log('打印预览窗口已加载完成'); 75 | } 76 | } 77 | ``` 78 | 79 | 当打印预览窗口加载完成时,控制台会输出 “打印预览窗口已加载完成”。 80 | 81 | 82 | ## `previewBeforeOpenCallback` 83 | 84 | - **说明**:在预览框架的 iframe 加载前触发的回调。 85 | - **用途**:允许在 iframe 加载之前执行某些操作,如修改 DOM 结构或添加自定义的 CSS 样式。 86 | 87 | ### 示例: 88 | 89 | ```javascript 90 | const printObj = { 91 | el: '#printMe', 92 | preview: true, 93 | previewBeforeOpenCallback() { 94 | console.log('打印预览 iframe 即将加载'); 95 | } 96 | } 97 | ``` 98 | 99 | 当打印预览的 iframe 即将加载时,控制台会输出 “打印预览 iframe 即将加载”。 100 | 101 | ## 使用钩子的场景 102 | 103 | 1. **打印前进行数据处理**:通过 `beforeOpenCallback`,你可以在打印前调整内容、修改样式或进行必要的 DOM 操作。 104 | 2. **用户交互的记录**:通过 `openCallback` 和 `closeCallback`,你可以监控用户的打印行为,并在合适的时机记录用户操作或执行清理工作。 105 | 3. **复杂打印需求**:使用 `previewOpenCallback` 来自定义打印预览行为,帮助更复杂的业务场景提供更好的打印体验。 106 | 107 | --- 108 | 109 | 这些生命周期钩子为 `VuePrintNext` 插件提供了灵活性,用户可以根据自己的业务需求,在不同的阶段执行自定义操作。通过这些钩子,你可以更加灵活地控制打印流程,确保打印输出符合预期。 110 | -------------------------------------------------------------------------------- /docs/guide/migration-from-vue-print-nb.md: -------------------------------------------------------------------------------- 1 | # 从 vue-print-nb 迁移到 vue-print-next 2 | 3 | 本文档将帮助您从 `vue-print-nb` / `vue3-print-nb` 迁移到 `vue-print-next`,详细说明两个库之间的参数对应关系、新增功能和使用方法变化。 4 | 5 | `vue-print-next` 几乎完全兼容 `vue-print-nb` 的 API,同时在功能和类型支持上进行了全面升级。它提供了更完善的类型定义以及更多实用特性,使得从 `vue-print-nb` 迁移变得简单直接。无论是在使用 Vue 2 还是 Vue 3,都能轻松完成迁移并获得更好的开发体验。 6 | 7 | ## 为什么要迁移? 8 | 9 | `vue-print-next` 是基于 `vue-print-nb` 完全重写的版本,它提供了以下优势: 10 | 11 | - **更好的 TypeScript 支持**:完全使用 TypeScript 重写,提供更好的类型提示和代码补全 12 | - **更好的 Vue 3 支持**:全面支持 Vue 3 的 setup 函数和组合式 API 13 | - **更多功能**:优化了打印预览界面,新增了自定义纸张尺寸、暗黑模式等功能 14 | 15 | ## 参数对照表 16 | 17 | 下表展示了 `vue-print-nb` 和 `vue-print-next` 之间的参数对应关系: 18 | 19 | | vue-print-nb 参数 | vue-print-next 参数 | 说明 | 类型变化 | 默认值变化 | 20 | |-------------------|-------------------|------|---------|----------| 21 | | id | el | 打印区域的选择器或DOM节点 | String → String \| HTMLElement | - | 22 | | standard | standard | 文档类型 | String → 'strict' \| 'loose' \| 'html5' | html5 | 23 | | extraHead | extraHead | 添加到``的额外节点 | String → String | - | 24 | | extraCss | extraCss | 添加的CSS样式表 | String → String | - | 25 | | popTitle | popTitle | 打印窗口的标题 | String → String | - | 26 | | openCallback | openCallback | 打印工具调用成功的回调函数 | Function → Function | - | 27 | | closeCallback | closeCallback | 关闭打印工具的回调函数 | Function → Function | - | 28 | | beforeOpenCallback | beforeOpenCallback | 调用打印工具前的回调函数 | Function → Function | - | 29 | | url | url | 打印指定URL的内容 | String → String | - | 30 | | asyncUrl | asyncUrl | 通过回调返回URL | Function → Function | - | 31 | | preview | preview | 是否启用预览模式 | Boolean → Boolean | false | 32 | | previewTitle | previewTitle | 预览窗口的标题 | String → String | '打印预览' → '打印预览' | 33 | | previewPrintBtnLabel | previewPrintBtnLabel | 预览窗口中打印按钮的文本 | String → String | '打印' → '打印' | 34 | | zIndex | zIndex | 预览窗口的z-index值 | String,Number → Number | 20002 | 35 | | previewBeforeOpenCallback | previewBeforeOpenCallback | 预览窗口打开前的回调函数 | Function → Function | - | 36 | | previewOpenCallback | previewOpenCallback | 预览窗口完全打开后的回调函数 | Function → Function | - | 37 | 38 | ## vue-print-next 新增参数 39 | 40 | `vue-print-next` 相比 `vue-print-nb` 新增了以下参数: 41 | 42 | | 参数名 | 说明 | 类型 | 可选值 | 默认值 | 43 | |-------|------|------|-------|-------| 44 | | noPrintSelector | 忽略打印的元素选择器 | String[] \| String | - | - | 45 | | paperSize | 纸张尺寸 | String | 'A0'到'A8', 'Letter', 'Legal', 'Tabloid', 'custom' | 'A4' | 46 | | orientation | 纸张方向 | String | 'portrait', 'landscape' | 'portrait' | 47 | | customSize | 自定义纸张尺寸 | Object | - | - | 48 | | previewSize | 预览窗口的纸张尺寸 | String \| Object | 同 paperSize | 同 paperSize | 49 | | darkMode | 是否默认使用深色模式 | Boolean | - | false | 50 | | windowMode | 是否使用弹窗模式(非全屏) | Boolean | - | false | 51 | | defaultScale | 默认缩放比例 | Number | - | 1 | 52 | | previewTools | 预览工具配置 | Object \| Boolean | - | `{ zoom: true, theme: true, fullscreen: true }` | 53 | 54 | ## 使用方式变化 55 | 56 | ### 1. 安装方式 57 | 58 | **vue-print-nb:** 59 | 60 | ```bash 61 | npm install vue-print-nb --save 62 | ``` 63 | 64 | **vue-print-next:** 65 | 66 | ```bash 67 | npm install vue-print-next --save 68 | # 或 69 | yarn add vue-print-next 70 | # 或 71 | pnpm add vue-print-next 72 | ``` 73 | 74 | ### 2. 全局注册 75 | 76 | **vue-print-nb (Vue 3):** 77 | 78 | ```javascript 79 | import { createApp } from 'vue' 80 | import App from './App.vue' 81 | import print from 'vue-print-nb' 82 | 83 | const app = createApp(App) 84 | app.use(print) 85 | app.mount('#app') 86 | ``` 87 | 88 | **vue-print-next (Vue 3):** 89 | 90 | ```javascript 91 | import { createApp } from 'vue' 92 | import App from './App.vue' 93 | import { printPlugin } from 'vue-print-next' 94 | 95 | const app = createApp(App) 96 | app.use(printPlugin) 97 | app.mount('#app') 98 | ``` 99 | 100 | ### 3. 局部注册指令 101 | 102 | **vue-print-nb (Vue 3):** 103 | 104 | ```html 105 | 115 | ``` 116 | 117 | **vue-print-next (Vue 3):** 118 | 119 | ```html 120 | 124 | 125 | 129 | ``` 130 | 131 | 或者在选项式API中: 132 | 133 | ```html 134 | 143 | ``` 144 | 145 | ### 4. 使用类进行手动调用 146 | 147 | **vue-print-nb:** 148 | 149 | ```txt 150 | 不支持 151 | ``` 152 | 153 | **vue-print-next:** 154 | 155 | ```javascript 156 | import { VuePrintNext } from 'vue-print-next' 157 | 158 | function handlePrint() { 159 | new VuePrintNext({ 160 | el: '#printMe', 161 | // 其他参数 162 | }) 163 | } 164 | ``` 165 | 166 | ## 迁移示例 167 | 168 | ### 基本打印示例 169 | 170 | **vue-print-nb:** 171 | 172 | ```html 173 | 181 | ``` 182 | 183 | **vue-print-next:** 184 | 185 | ```html 186 | 194 | ``` 195 | 196 | ### 高级配置示例 197 | 198 | **vue-print-nb:** 199 | 200 | ```html 201 | 209 | 210 | 222 | ``` 223 | 224 | **vue-print-next:** 225 | 226 | ```html 227 | 235 | 236 | 249 | ``` 250 | 251 | ## 常见问题 252 | 253 | ### 1. `id` 参数变更为 `el` 254 | 255 | 在 `vue-print-nb` 中,使用 `id` 参数指定打印区域,而在 `vue-print-next` 中,该参数已更改为 `el`,并且支持传入 DOM 节点或 CSS 选择器。 256 | 257 | ### 2. 如何使用新增的预览功能? 258 | 259 | `vue-print-next` 新增了强大的预览功能,可以通过设置 `preview: true` 启用: 260 | 261 | ```javascript 262 | const printObj = { 263 | el: '#printMe', 264 | preview: true, 265 | previewTitle: '打印预览', 266 | previewPrintBtnLabel: '打印' 267 | } 268 | ``` 269 | 270 | ### 3. 如何设置纸张大小和方向? 271 | 272 | `vue-print-next` 新增了纸张大小和方向设置: 273 | 274 | ```javascript 275 | const printObj = { 276 | el: '#printMe', 277 | paperSize: 'A4', // 设置纸张大小 278 | orientation: 'landscape' // 设置为横向打印 279 | } 280 | ``` 281 | 282 | ### 4. 如何使用自定义纸张大小? 283 | 284 | ```javascript 285 | const printObj = { 286 | el: '#printMe', 287 | paperSize: 'custom', 288 | customSize: { 289 | width: '100', 290 | height: '150', 291 | unit: 'mm' // 单位可以是 mm, cm, in, px 292 | } 293 | } 294 | ``` 295 | 296 | ### 5. 如何忽略不需要打印的元素? 297 | 298 | ```javascript 299 | const printObj = { 300 | el: '#printMe', 301 | noPrintSelector: '.no-print' // 可以是字符串或字符串数组 302 | } 303 | ``` 304 | 305 | ## 总结 306 | 307 | 从 `vue-print-nb` 迁移到 `vue-print-next` 相对简单,主要需要注意的是: 308 | 309 | 1. 将 `id` 参数更改为 `el` 310 | 2. 导入方式变更为 `import { printPlugin, vPrint, VuePrintNext } from 'vue-print-next'` 311 | 3. 利用新增的功能如预览模式、纸张设置等增强打印体验 312 | 313 | `vue-print-next` 在保持原有功能的基础上,提供了更多的功能和更好的类型支持,使打印功能的实现更加灵活和强大。 314 | -------------------------------------------------------------------------------- /docs/guide/preview-tools.md: -------------------------------------------------------------------------------- 1 | 9 | # 自定义预览工具栏 10 | 11 | 从 v1.1.1 版本开始,VuePrintNext 支持自定义预览工具栏中显示的功能按钮,让用户可以根据需要控制预览界面的交互元素。 12 | 13 | ## 配置选项 14 | 15 | 通过 `previewTools` 选项,你可以控制预览工具栏中显示的功能按钮: 16 | 17 | ```js 18 | useVuePrintNext({ 19 | el: '#print-content', 20 | preview: true, 21 | previewTools: { 22 | zoom: true, // 是否显示缩放控制 23 | theme: true, // 是否显示主题切换按钮 24 | fullscreen: true // 是否显示全屏切换按钮 25 | } 26 | }); 27 | ``` 28 | 29 | 你也可以通过设置 `previewTools: false` 来一次性关闭所有工具按钮: 30 | 31 | ```js 32 | useVuePrintNext({ 33 | el: '#print-content', 34 | preview: true, 35 | previewTools: false // 关闭所有工具按钮 36 | }); 37 | ``` 38 | 39 | ## 可配置的工具按钮 40 | 41 | | 选项 | 类型 | 默认值 | 说明 | 42 | | ---- | ---- | ------ | ---- | 43 | | `zoom` | `boolean` | `true` | 控制是否显示缩放控制(缩小、放大、重置按钮) | 44 | | `theme` | `boolean` | `true` | 控制是否显示主题切换按钮(深色/浅色模式) | 45 | | `fullscreen` | `boolean` | `true` | 控制是否显示全屏切换按钮(全屏/窗口模式) | 46 | 47 | ## 使用场景 48 | 49 | ### 简化界面 50 | 51 | 如果你希望为用户提供一个简化的预览界面,可以隐藏部分或全部工具按钮。 52 | 53 | 方式一:单独设置各个工具按钮 54 | 55 | ```js 56 | useVuePrintNext({ 57 | el: '#print-content', 58 | preview: true, 59 | previewTools: { 60 | zoom: false, 61 | theme: false, 62 | fullscreen: false 63 | } 64 | }); 65 | ``` 66 | 67 | 方式二:一次性关闭所有工具按钮 68 | 69 | ```js 70 | useVuePrintNext({ 71 | el: '#print-content', 72 | preview: true, 73 | previewTools: false 74 | }); 75 | ``` 76 | 77 | ### 限制用户交互 78 | 79 | 在某些场景下,你可能希望限制用户对预览内容的操作,例如禁止缩放但允许切换主题: 80 | 81 | ```js 82 | useVuePrintNext({ 83 | el: '#print-content', 84 | preview: true, 85 | previewTools: { 86 | zoom: false, // 禁用缩放 87 | theme: true, // 允许切换主题 88 | fullscreen: true // 允许全屏切换 89 | } 90 | }); 91 | ``` 92 | 93 | ### 固定预览模式 94 | 95 | 如果你希望预览始终以窗口模式显示,可以禁用全屏切换按钮并设置 `windowMode: true`: 96 | 97 | ```js 98 | useVuePrintNext({ 99 | el: '#print-content', 100 | preview: true, 101 | windowMode: true, // 默认使用窗口模式 102 | previewTools: { 103 | zoom: true, 104 | theme: true, 105 | fullscreen: false // 禁用全屏切换 106 | } 107 | }); 108 | ``` 109 | 110 | ## 注意事项 111 | 112 | - 即使隐藏了所有工具按钮,关闭按钮仍然会显示,以便用户可以关闭预览窗口。 113 | - 如果你禁用了某个功能按钮,用户将无法通过界面切换相应的功能,但你仍然可以通过其他配置选项设置初始状态(如 `darkMode`、`windowMode`、`defaultScale` 等)。 114 | -------------------------------------------------------------------------------- /docs/guide/what-is-vue-print-next.md: -------------------------------------------------------------------------------- 1 | # 什么是 VuePrintNext? 2 | 3 | `VuePrintNext` 是一个功能强大、轻量级的 Vue 打印插件,支持 Vue 2 和 Vue 3 环境下的打印功能。它基于 [vue3-print-nb](https://github.com/Power-kxLee/vue3-print-nb) 插件进行了改进和重写,全面采用 TypeScript 进行开发,旨在为 Vue 3 提供更好的支持和扩展性。 4 | 5 | 通过 `VuePrintNext`,开发者可以快速实现网页内容的全局打印、局部打印,以及打印预览功能,支持高度自定义的打印行为,适合多种场景下的打印需求。 6 | 7 | ## VuePrintNext 的主要特性: 8 | 9 | 1. **兼容 Vue 2 和 Vue 3**:无论是指令式调用还是通过 API 调用,`VuePrintNext` 可以在 Vue 2 和 Vue 3 环境下无缝工作。 10 | 11 | 2. **多种打印模式**:支持全页面打印和指定区域的局部打印,只需简单的指令或 API 调用即可实现。 12 | 13 | 3. **打印预览**:用户可以在实际打印前,预览打印内容,确保打印效果符合预期。 14 | 15 | 4. **样式控制**:可以通过 `extraCss` 添加额外的样式文件,或者通过 `noPrintSelector` 指定不需要打印的元素,灵活定制打印内容的样式。 16 | 17 | 5. **多方式选择打印区域**:支持通过 DOM 节点或 CSS 选择器指定需要打印的区域,增强了打印的灵活性。 18 | 19 | 6. **异步加载 URL 内容**:可以指定远程 URL 作为打印内容,并支持异步加载远程资源,满足动态数据打印需求。 20 | 21 | 7. **轻量且易于集成**:`VuePrintNext` 轻量简洁,安装和使用非常简单,适合各种项目需求,尤其是需要在复杂应用中实现局部或自定义打印的场景。 22 | 23 | 24 | ## 适用场景 25 | 26 | `VuePrintNext` 非常适合需要打印复杂内容的场景,例如: 27 | - 打印发票、合同、清单等文档。 28 | - 打印单页应用中的特定部分,而不是整个页面。 29 | - 生成带有自定义样式的打印预览,并允许用户选择打印内容。 30 | - 在动态生成内容时,支持通过异步加载打印远程或异步生成的数据。 31 | 32 | `VuePrintNext` 提供了一个高效且灵活的打印解决方案,能够帮助开发者轻松集成各种打印功能到他们的 Vue 应用中。 33 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Vue Print Next" 7 | text: "Vue 打印插件" 8 | tagline: "高效、灵活的打印解决方案,支持 Vue 2 和 Vue 3。" 9 | actions: 10 | - theme: brand 11 | text: 快速上手 12 | link: /guide/what-is-vue-print-next 13 | - theme: alt 14 | text: 在线演示 15 | link: https://alexpang.cn/vue-print-next/vue3-demo 16 | image: 17 | src: /logo.png 18 | alt: Vue Print Next 19 | 20 | features: 21 | - icon: 🚀 22 | title: 简单易用 23 | details: 提供简单的 API,允许用户快速集成和使用打印功能,无论是全局注册还是局部调用指令都非常方便。 24 | - icon: 💪 25 | title: 高效打印 26 | details: 支持高效的打印操作,能够快速生成和打印文档,提供实时预览功能,以确保打印效果与预期一致。 27 | - icon: 🎨 28 | title: 灵活配置 29 | details: 允许用户通过丰富的参数配置打印选项,包括文档类型、打印预览、忽略特定元素等,满足各种打印需求。 30 | - icon: 📑 31 | title: 完善的 TypeScript 支持 32 | details: 使用 TypeScript 开发,提供全面的类型定义和智能提示,帮助开发者在编码过程中减少错误并提高开发效率。 33 | - icon: 🔄 34 | title: Vue2 和 Vue3 兼容 35 | details: 兼容 Vue2 和 Vue3,支持指令的无缝过渡,使得项目升级或混合使用 Vue 版本时无缝集成。 36 | - icon: 🌐 37 | title: URL 打印支持 38 | details: 支持打印指定 URL 内容,确保用户能够打印在线内容,并处理异步加载问题,支持跨域资源打印。 39 | - icon: 🖼️ 40 | title: 详细样式控制 41 | details: 允许用户通过额外的 CSS 和 `<head>` 内容进行详细样式控制,确保打印内容完全符合设计要求。 42 | - icon: 🔍 43 | title: 打印内容检查 44 | details: 提供打印预览功能,让用户在实际打印前可以检查和调整打印内容,确保最终打印效果与设计一致,避免错误和不必要的修改。 45 | 46 | --- 47 | 67 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "dev": "vitepress dev", 8 | "build": "vitepress build", 9 | "preview": "vitepress preview" 10 | }, 11 | "author": "zi.yang (https://github.com/Alessandro-Pang)", 12 | "license": "MIT", 13 | "description": "", 14 | "devDependencies": { 15 | "vitepress": "^1.6.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alessandro-Pang/vue-print-next/af8f1d342e4845cf991de66a8fbe00862d106abc/docs/public/logo.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-print-next", 3 | "version": "1.1.3", 4 | "author": "zi.yang (https://github.com/Alessandro-Pang)", 5 | "license": "MIT", 6 | "type": "module", 7 | "keywords": [ 8 | "vue-print", 9 | "vue3-print", 10 | "print", 11 | "vue-print-nb", 12 | "vue3-print-nb" 13 | ], 14 | "description": "This is a directive wrapper for printed, Simple, fast, convenient, light.", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/Alessandro-Pang/vue-print-next.git" 18 | }, 19 | "homepage": "https://alexpang.cn/vue-print-next/docs/", 20 | "bugs": "https://github.com/Alessandro-Pang/vue-print-next/issues", 21 | "types": "types/index.d.ts", 22 | "files": [ 23 | "types", 24 | "dist" 25 | ], 26 | "main": "./dist/vue-print-next.umd.cjs", 27 | "module": "./dist/vue-print-next.js", 28 | "exports": { 29 | ".": { 30 | "import": { 31 | "types": "./types/index.d.ts", 32 | "default": "./dist/vue-print-next.js" 33 | }, 34 | "require": { 35 | "types": "./types/index.d.ts", 36 | "default": "./dist/vue-print-next.umd.cjs" 37 | } 38 | } 39 | }, 40 | "scripts": { 41 | "dev": "vite", 42 | "build": "vue-tsc -b && vite build", 43 | "build:watch": "vue-tsc -b && vite build --watch", 44 | "serve": "vite preview" 45 | }, 46 | "peerDependencies": { 47 | "vue": "^2.0.0 || >=3.0.0" 48 | }, 49 | "devDependencies": { 50 | "@types/node": "^22.15.17", 51 | "@vitejs/plugin-vue": "^5.0.5", 52 | "typescript": "^5.5.3", 53 | "vite": "^6.3.5", 54 | "vue": "^3.4.33", 55 | "vue-tsc": "^2.0.26" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'demos/*' 3 | - 'docs' 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alessandro-Pang/vue-print-next/af8f1d342e4845cf991de66a8fbe00862d106abc/public/favicon.ico -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alessandro-Pang/vue-print-next/af8f1d342e4845cf991de66a8fbe00862d106abc/public/logo.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alessandro-Pang/vue-print-next/af8f1d342e4845cf991de66a8fbe00862d106abc/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 8 | 24 | 25 | 55 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | #app { 2 | font-family: Avenir, Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | text-align: center; 6 | color: #2c3e50; 7 | margin-top: 60px; 8 | } 9 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp, } from 'vue' 2 | import App from './App.vue' 3 | import './index.css' 4 | const app = createApp(App) 5 | app.mount('#app') 6 | -------------------------------------------------------------------------------- /src/print/index.ts: -------------------------------------------------------------------------------- 1 | import vPrint from './packages/vPrint.ts'; 2 | import VuePrintNext from './packages/VuePrintNext.ts' 3 | import type {App} from "vue"; 4 | 5 | /** 6 | * 用于 vue.use 安装指令插件 7 | */ 8 | const printPlugin = { 9 | install(Vue: App) { 10 | Vue.directive(vPrint.directiveName, vPrint); 11 | } 12 | } 13 | 14 | 15 | export { 16 | vPrint, 17 | printPlugin, 18 | VuePrintNext, 19 | } 20 | -------------------------------------------------------------------------------- /src/print/packages/vPrint.ts: -------------------------------------------------------------------------------- 1 | import VuePrintNext from './VuePrintNext'; 2 | import type { DirectiveBinding } from 'vue'; 3 | import { PrintAreaOption } from '../../../types'; 4 | 5 | /** 6 | * @file 打印 7 | * 指令 `v-print`,默认打印整个窗口。 8 | * 传入参数 `v-print="'#id'"` ,参数为需要打印的局部容器标识符。 9 | */ 10 | const addEvent = (element: HTMLElement, type: string, callback: () => void) => { 11 | if (element.addEventListener) { 12 | element.addEventListener(type, callback, false); 13 | } else if ((element as any).attachEvent) { 14 | (element as any).attachEvent('on' + type, callback); 15 | } else { 16 | (element as any)['on' + type] = callback; 17 | } 18 | }; 19 | 20 | const vPrint = { 21 | directiveName: 'print', 22 | // vue3 指定挂载 23 | mounted(el: HTMLElement, binding: DirectiveBinding) { 24 | let printElement: HTMLElement | string | undefined; 25 | let options: PrintAreaOption = {} as PrintAreaOption; 26 | 27 | addEvent(el, 'click', () => { 28 | // 全屏打印时不需要传入任何参数 29 | if(!binding.value) { 30 | printElement = 'body' 31 | }else if (typeof binding.value === 'string') { 32 | printElement = binding.value; 33 | } else if (typeof binding.value === 'object') { 34 | printElement = binding.value.el; 35 | options = binding.value; 36 | } 37 | new VuePrintNext({ ...options, el: printElement, vue: binding.instance }); 38 | }); 39 | }, 40 | // 兼容 Vue2 指令挂载 41 | bind(el: HTMLElement, binding: DirectiveBinding, vnode: any) { 42 | binding.instance = vnode.context; 43 | vPrint.mounted(el, binding); 44 | }, 45 | }; 46 | 47 | export default vPrint; 48 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 5 | "target": "ES2020", 6 | "useDefineForClassFields": true, 7 | "module": "ESNext", 8 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 9 | "skipLibCheck": true, 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "moduleDetection": "force", 17 | "noEmit": true, 18 | "jsx": "preserve", 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true, 25 | }, 26 | "include": [ 27 | "src/**/*.ts", 28 | "src/**/*.tsx", 29 | "src/**/*.vue", 30 | "types/*" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 5 | "skipLibCheck": true, 6 | "module": "ESNext", 7 | "moduleResolution": "bundler", 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "noEmit": true 11 | }, 12 | "include": ["vite.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { App, DirectiveBinding } from 'vue'; 2 | 3 | // 纸张常见尺寸类型 4 | export type PaperSize = 'A0' | 'A1' | 'A2' | 'A3' | 'A4' | 'A5' | 'A6' | 'A7' | 'A8' | 'Letter' | 'Legal' | 'Tabloid' | 'custom'; 5 | 6 | // 纸张方向类型 7 | export type Orientation = 'portrait' | 'landscape'; 8 | 9 | // 自定义尺寸接口 10 | export interface CustomSize { 11 | width: string | number; 12 | height: string | number; 13 | unit?: 'mm' | 'cm' | 'in' | 'px'; 14 | } 15 | 16 | // 预览工具配置接口 17 | export interface PreviewToolsOption { 18 | // 是否显示缩放控制 19 | zoom?: boolean; 20 | // 是否显示主题切换按钮 21 | theme?: boolean; 22 | // 是否显示全屏切换按钮 23 | fullscreen?: boolean; 24 | } 25 | 26 | export interface PrintAreaOption { 27 | // 局部打印的元素,支持 css 选择器或 dom 节点 28 | el?: string | HTMLElement, 29 | // 文档类型,默认是html5,可选 html5,loose,strict 30 | standard?: 'strict' | 'loose' | 'html5'; 31 | // 忽略打印元素的的选择器 32 | noPrintSelector?: string[] | string; 33 | // 打印指定的网址,这里不能跟id共存 如果共存id的优先级会比较高 34 | url?: string; 35 | // 异步 URL 回调函数 36 | asyncUrl?: (callback: (url: string) => void, vueInstance: any) => void; 37 | // 是否启动预览模式 38 | preview?: boolean; 39 | // 附加在head标签上的额外标签,使用逗号分隔 40 | extraHead?: string; 41 | // 额外的css连接,多个逗号分开 42 | extraCss?: string; 43 | // 打印时页眉的title 44 | popTitle?: string; 45 | // 打印预览的标题 46 | previewTitle?: string; 47 | // 打印预览的标题 48 | previewPrintBtnLabel?: string; 49 | // 预览窗口的 z-index 50 | zIndex?: number; 51 | // 纸张尺寸,默认为 A4 52 | paperSize?: PaperSize; 53 | // 纸张方向,默认为纵向 54 | orientation?: Orientation; 55 | // 自定义纸张尺寸,仅当 paperSize 为 'custom' 时生效 56 | customSize?: CustomSize; 57 | // 预览窗口的纸张尺寸,默认跟随paperSize,可单独设置 58 | previewSize?: PaperSize | CustomSize; 59 | // 是否默认使用深色模式 60 | darkMode?: boolean; 61 | // 是否默认使用弹窗模式(非全屏) 62 | windowMode?: boolean; 63 | // 默认缩放比例 64 | defaultScale?: number; 65 | // 预览工具配置,控制显示哪些工具按钮,设置为false时关闭所有工具按钮 66 | previewTools?: PreviewToolsOption | false; 67 | // 预览窗口打开之前的 callback 68 | previewBeforeOpenCallback?: (vue?: any) => void; 69 | // 预览窗口打开之后的 callback 70 | previewOpenCallback?: (vue?: any) => void; 71 | // 调用打印工具的成功回调函数 72 | beforeOpenCallback?: (vue?: any) => void; 73 | // 调用打印之后的回调事件 74 | openCallback?: (vue?: any) => void; 75 | // 关闭打印工具成功的回调函数 76 | closeCallback?: (vue?: any) => void; 77 | // vue 实例,使用指令时自动获取,当使用方法调用时需手动传入 78 | vue?: any; 79 | } 80 | 81 | export interface PrintAreaWindow { 82 | f: HTMLIFrameElement; 83 | win: Window | HTMLIFrameElement; 84 | doc: Document; 85 | } 86 | 87 | export type Standards = { 88 | strict: 'strict'; 89 | loose: 'loose'; 90 | html5: 'html5'; 91 | }; 92 | 93 | export class VuePrintNext { 94 | private readonly standards: Standards; 95 | private iframeId: string; 96 | private previewBody: HTMLElement | null; 97 | private close: HTMLElement | null; 98 | private scale: number; 99 | private isDarkMode: boolean; 100 | private isFullscreen: boolean; 101 | private settings: PrintAreaOption; 102 | constructor(option: PrintAreaOption); 103 | 104 | private init(): void; 105 | private addEvent(element: HTMLElement | null, type: string, callback: EventListenerOrEventListenerObject): void; 106 | private previewIframeLoad(): void; 107 | private removeCanvasImg(): void; 108 | private print(iframe: PrintAreaWindow): void; 109 | private write(PADocument: Document): void; 110 | private docType(): string; 111 | private getHead(): string; 112 | private getBody(): string; 113 | private beforeHandler(printContentDom: HTMLElement): HTMLElement; 114 | private getFormData(ele: HTMLElement): HTMLElement; 115 | private getPrintWindow(url: string): PrintAreaWindow; 116 | private previewBoxShow(): void; 117 | private previewBoxHide(): void; 118 | private previewBox(): { close: HTMLElement | null; previewBody: HTMLElement | null }; 119 | private iframeBox(frameId: string, url: string): HTMLIFrameElement; 120 | } 121 | 122 | export const vPrint: { 123 | directiveName: string; 124 | mounted(el: HTMLElement, binding: DirectiveBinding): void; 125 | bind(el: HTMLElement, binding: DirectiveBinding, vnode: any): void; 126 | } 127 | 128 | export const printPlugin: { 129 | install(vue: App): void; 130 | } 131 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import path from 'node:path' 4 | import { fileURLToPath } from 'node:url'; 5 | 6 | const filename = fileURLToPath(import.meta.url); 7 | const dirname = path.dirname(filename); 8 | 9 | // https://vitejs.dev/config/ 10 | export default defineConfig({ 11 | plugins: [vue()], 12 | build: { 13 | target: 'es2015', 14 | lib: { 15 | entry: path.resolve(dirname, 'src/print/index.ts'), 16 | formats: ['es','umd'], 17 | name: 'VuePrintNext' 18 | }, 19 | rollupOptions: { 20 | // 请确保外部化那些你的库中不需要的依赖 21 | external: ['vue'], 22 | output: { 23 | // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量 24 | globals: { 25 | vue: 'Vue' 26 | } 27 | } 28 | } 29 | } 30 | }) 31 | --------------------------------------------------------------------------------