page2pdf_server 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -23
- package/config/default.json +1 -1
- package/package.json +102 -105
- package/src/CSS/345/205/274/345/256/271/346/200/247.txt +69 -0
- package/src/app.ts +7 -2
- package/src/components/home/controller.ts +2 -2
- package/src/components/home/pdfController.ts +9 -5
- package/src/components/home/services.ts +3 -3
- package/src/components/home/splitController.ts +9 -5
- package/src/components/home/validators.ts +1 -1
- package/src/db/home.ts +3 -3
- package/src/helpers/apiResponse.ts +1 -1
- package/src/helpers/error/TimeOutError.ts +1 -1
- package/src/helpers/loggers.ts +1 -1
- package/src/index.ts +4 -1
- package/src/middlewares/errorHandler.ts +1 -1
- package/src/routes/index.ts +2 -2
- package/src/types/request/config.ts +10 -5
- package/src/types/request/home.ts +1 -1
- package/src/utils/crypt.ts +12 -13
- package/src/utils/pdfgen.ts +66 -16
- package/src//346/265/213/350/257/225.txt +28 -2
- package/test//346/211/223/345/215/260/346/234/215/345/212/241.http +17 -0
- package/.idea/codeStyles/Project.xml +0 -58
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/encodings.xml +0 -7
- package/.idea/inspectionProfiles/Project_Default.xml +0 -7
- package/.idea/modules.xml +0 -8
- package/.idea/page2pdf-server.iml +0 -12
- package/.idea/tenstack-starter-main.iml +0 -12
- package/.idea/vcs.xml +0 -6
package/README.md
CHANGED
|
@@ -42,12 +42,17 @@ chrome-remote-interface不会为您启动Chrome。本来想用chrome-remote-inte
|
|
|
42
42
|
注意,动态网页在Chrome浏览器直接打印预览的效果应该和本软件转换输出的pdf一致效果,为打印设置的css必须在应用层中自己去添加的。
|
|
43
43
|
纯粹静态html的打印转换pdf,且是标准纸张大小的横竖纸张方向掺杂的这种情况:考虑一次提取一部分pageRanges[]吗,分解多个的files[url,out]独立生成之后做合并。
|
|
44
44
|
|
|
45
|
-
## Prerequisites
|
|
45
|
+
## Prerequisites 必须配置的
|
|
46
46
|
|
|
47
|
-
- [Node.js](https://nodejs.org) (`>=
|
|
47
|
+
- [Node.js](https://nodejs.org) (`>= 20.0.0`)
|
|
48
48
|
- [Yarn](https://yarnpkg.com/en/docs/install) or [NPM](https://docs.npmjs.com/getting-started/installing-node)
|
|
49
|
-
-
|
|
50
|
-
|
|
49
|
+
- 客户电脑必须有Chrome浏览器;
|
|
50
|
+
- 要点击谷歌浏览器的链接,要配置端口号,启动命令设置:目标修改: C:\Users\herzhang\AppData\Local\Google\Chrome\Application\chrome.exe --remote-debugging-port=9872
|
|
51
|
+
比如: "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9872
|
|
52
|
+
- 之后服务期间,需要首先就该点击该链接开启Chrome浏览器。
|
|
53
|
+
- 特别注意:windows用户若非管理员账户的:桌面上的Chrome快捷方式右键点属性在“兼容性”设置中:必须勾选“管理员身份运行”;
|
|
54
|
+
注意浏览器的管理员和普通身份运行的效果进入显示都不同的。目标URL网站需要登陆处理的,登陆点击后浏览器没有反馈,需直接关闭该TAB页面。
|
|
55
|
+
-报错Error: connect ECONNREFUSED 127.0.0.1:9872 【解决】先在windows任务管理器把chrome.exe的进程全部关闭后重来。
|
|
51
56
|
## Install
|
|
52
57
|
|
|
53
58
|
- Fork or Use [this](https://github.com/filoscoder/tenstack-starter/generate) template repository.
|
|
@@ -57,8 +62,10 @@ chrome-remote-interface不会为您启动Chrome。本来想用chrome-remote-inte
|
|
|
57
62
|
> Make sure you already have [`node.js`](https://github.com/filoscoder/tenstack-starter#prerequisites) and [`npm`](https://github.com/filoscoder/tenstack-starter#prerequisites) or [`yarn`](https://github.com/filoscoder/tenstack-starter#prerequisites) installed in your system.
|
|
58
63
|
|
|
59
64
|
- Set your `git remote add origin` path
|
|
60
|
-
-
|
|
61
|
-
|
|
65
|
+
- 用户电脑也需要做些安装的:
|
|
66
|
+
只需要本工程目录的 /config, /dist, 文书打印转换器.bat,新/pdfs目录, 并且页已经node.js, nodemon;
|
|
67
|
+
还必须要安装工程目录/package.json; /module-alias/的目录全部依赖包的。
|
|
68
|
+
-
|
|
62
69
|
```bash
|
|
63
70
|
git remote add origin ${forked-and-cloned-path}
|
|
64
71
|
```
|
|
@@ -82,7 +89,9 @@ chrome-remote-interface不会为您启动Chrome。本来想用chrome-remote-inte
|
|
|
82
89
|
<br>
|
|
83
90
|
<br>
|
|
84
91
|
|
|
85
|
-
## Alias @
|
|
92
|
+
## Alias 其它,对比 @
|
|
93
|
+
Puppeteer 是一个基于Chrome调试协议CDP 构建的额外高层 API,除其他功能外,它可以启动并使用捆绑版本的 Chromium,而非系统上安装的版本。
|
|
94
|
+
我这个工具目前使用CDP协议构建的,而不是利用Puppeteer做的。
|
|
86
95
|
|
|
87
96
|
To make paths clean and ease to access `@` is setup up for `/src` path
|
|
88
97
|
|
|
@@ -203,25 +212,31 @@ If you have any question or suggestion, don't hesitate to contact me:
|
|
|
203
212
|
|
|
204
213
|
注意,如果发送post请求的body底下的内容实际为空的话,本转换器默认执行:把工作目录的所有pdf文件按照修改时间顺序进行合并,默认输出文件名=配置好的名字。
|
|
205
214
|
假若某个URL不正常的,很可能无法触发页面加载完成,导致作业一致无法继续下去,大大超出预期执行时间。URL对应网页若不是一次性加载完成还会陆陆续续修改内容的就是这种情况造成死等长时间没结果。
|
|
215
|
+
# 调试 "start": "NODE_ENV=development run-s prettify lint & nodemon" 用Debug,异步执行,打断块点有点毛病
|
|
206
216
|
|
|
207
217
|
# 页眉页脚配置html脚本转为json后
|
|
208
218
|
例子,用https://uutool.cn/html2json/ 工具可转换,转换才能通过api/pdf发送命令的。
|
|
209
|
-
"
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
"</div
|
|
215
|
-
]
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
219
|
+
{ name: (original ? "记录" : "报告") + rep?.isp?.no,
|
|
220
|
+
singleTab: true,
|
|
221
|
+
lay: {
|
|
222
|
+
head: [
|
|
223
|
+
'<div style=\\"position: relative; width:100%; text-align:center; border-bottom: 1pt solid #eeeeee; margin: 3.5mm 0px 10px; font-size: 10pt\\">',
|
|
224
|
+
`<div style=\\"position: absolute; width:100%; text-align:left; bottom: 5px; left: 50px;\\">报告No: ${rep?.isp?.no}</div></div>`
|
|
225
|
+
],
|
|
226
|
+
foot: [
|
|
227
|
+
'<div style=\\"position: relative; width: 100%; text-align: left; border-top: 1pt solid #eeeeee; margin: 10px 0px 1.5mm; font-size: 8pt;\\">',
|
|
228
|
+
'<div style=\\"position: absolute; width: 100%; text-align: center; top: 5px;\\">共<span>~pageNumber~</span>页 / 第<span>~totalPages~</span>页</div></div>'
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
files: [
|
|
232
|
+
{
|
|
233
|
+
url,
|
|
234
|
+
out: `tmp-${rep?.isp?.no}` + (original ? "-O" : ""),
|
|
235
|
+
headFrom: 3,
|
|
236
|
+
frNo: 3,
|
|
237
|
+
},
|
|
238
|
+
] }
|
|
239
|
+
其中 -NOT_DISPLAY- 指示若没有页码的就不显示相关局部部分。
|
|
225
240
|
# 注意适用性
|
|
226
241
|
本文书打印转换器只能适用单用户在客户本机用,每次执行一个打印指令,不要并行发出指令,本机工作目录也是临时文件,针对当前指令适用的,不能存放无关文件。
|
|
227
242
|
本转换器console命令输出窗口可实时观察到当前打印转换指令的执行过程输出提示,同时目标浏览器也会新开启浏览窗口,这些由本转换器自动开启的浏览窗口实际上
|
|
@@ -237,3 +252,19 @@ If you have any question or suggestion, don't hesitate to contact me:
|
|
|
237
252
|
{"name":"D4", "css": "10.4in 12.6in","w": 10.4,"h": 12.6}
|
|
238
253
|
]
|
|
239
254
|
}
|
|
255
|
+
# 拒绝连接的
|
|
256
|
+
对Chrome 右键 点选 以管理员身份运行;
|
|
257
|
+
# 动态布局问题
|
|
258
|
+
只用css做动态的组件和用了js脚本做动态布局的组件在打印时刻不同,比如"customize-easy-ui-component"库的LineColumnFlex和LineColumn的差别即使这样,
|
|
259
|
+
前者在打印时刻需要约束浏览器窗口的宽度为纸张输出的宽度差不多的大小,后者可打印页数就可能和内容页数不一致,后者只用css的就不会有这个麻烦。
|
|
260
|
+
就算同一个组件有可能出现:同一次打印出现两种布局状态。比如这样的:<LineColumnFlex 1> {useMediaQuery({query: 'print'}) && <LineColumnFlex 2>}的,
|
|
261
|
+
前面一个是js按照浏览器打印触发之前的div宽度来布局的有5列,然而后面哪一个是按照最终纸张输出宽度来做布局的才2列,完全不一样了!但是后面第二个组件可能输出不完整的,
|
|
262
|
+
因为打印纸张的输出页数实际在useMediaQuery({query: 'print'})逻辑条件成立以前就敲定了可打印纸张数量!!所以有很大问题。js动态代码简单,css动态写代码麻烦。
|
|
263
|
+
就接着这个例子讲:用{useMediaQuery({query: 'print'})&&逻辑嵌套的哪些内容,就算我约束了浏览器宽度为纸张宽度以后,实际上在打印敲定输出页数时间这些逻辑嵌套的
|
|
264
|
+
需要打印内容实际上也并没有计算在立刻敲定输出页数之内的,因此也必然导致打印页数丢失或不够的问题的,这个情况必须用css,除非不打印比打印语境内容占用高度还少的话。
|
|
265
|
+
<div css={{"@media not print": {display: 'none'}}}>
|
|
266
|
+
<LineColumnFlex column={7}>
|
|
267
|
+
</div>
|
|
268
|
+
如上代码会有严重毛病:打印输出实际上只有一次机会render;useMeasure(ref还是空的)div宽度=0导致实际只有输出一列的布局,打印没机会修正了,屏幕情况可以不限次数render,
|
|
269
|
+
可是打印就不行了,上面这情况太特殊了。
|
|
270
|
+
|
package/config/default.json
CHANGED
package/package.json
CHANGED
|
@@ -1,105 +1,102 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "page2pdf_server",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "文书打印转换器",
|
|
5
|
-
"private": false,
|
|
6
|
-
"authors": "herzhang",
|
|
7
|
-
"repository": "https://gitee.com/heerzhang/page2pdf-server",
|
|
8
|
-
"license": "MIT",
|
|
9
|
-
"scripts": {
|
|
10
|
-
"yarn": "yarn",
|
|
11
|
-
"yarn命令行": "yarn add nodemon -D",
|
|
12
|
-
"注意2": "运行prettify来修复代码,免去到处是标记红色的,太严格了",
|
|
13
|
-
"prettify": "prettier --write \"src/**/*.{ts,js,json}\" \"__tests__/**/*.{ts,js,json}\"",
|
|
14
|
-
"start": "NODE_ENV=development run-s prettify lint & nodemon",
|
|
15
|
-
"build": "cross-env NODE_ENV=production run-s prettify clean transpile",
|
|
16
|
-
"lint": "eslint 'src/**/*.{ts,js}' '__tests__/**/*.{ts,js}'",
|
|
17
|
-
"lint:fix": "eslint --fix 'src/**/*.{ts,js}' '__tests__/**/*.{ts,js}' --quiet",
|
|
18
|
-
"有些隐藏错误": "需要运行watch才发现",
|
|
19
|
-
"watch": "tsc --watch",
|
|
20
|
-
"clean": "rimraf dist",
|
|
21
|
-
"test": "cross-env NODE_ENV=test jest --coverage --runInBand --detectOpenHandles",
|
|
22
|
-
"单步调试": "不必运行test,直接到__test__目录文件直接挑选修改测试项并调试",
|
|
23
|
-
"test:watchAll": "cross-env NODE_ENV=test jest --watchAll --runInBand --detectOpenHandles",
|
|
24
|
-
"transpile": "tsc",
|
|
25
|
-
"service:start": "pm2 start ecosystem.config.js",
|
|
26
|
-
"service:reload": "pm2 reload ecosystem.config.js",
|
|
27
|
-
"service:startup": "pm2 startup",
|
|
28
|
-
"service:stop": "pm2 stop ecosystem.config.js",
|
|
29
|
-
"service:list": "pm2 list ecosystem.config.js",
|
|
30
|
-
"service:delete": "pm2 delete ecosystem.config.js",
|
|
31
|
-
"service:logs": "pm2 logs",
|
|
32
|
-
"hook主动运行": "操作系统缘故,将单引号改成双引号,加转义符,提交代码会自动执行;git提交给它勾掉吧,yarn会自动运行prepare",
|
|
33
|
-
"prepare": "husky install"
|
|
34
|
-
},
|
|
35
|
-
"keywords": [
|
|
36
|
-
"dynamic page pdf generator",
|
|
37
|
-
"html to pdf",
|
|
38
|
-
"app url to pdf",
|
|
39
|
-
"pdf generation",
|
|
40
|
-
"node.js",
|
|
41
|
-
"express",
|
|
42
|
-
"print",
|
|
43
|
-
"server"
|
|
44
|
-
],
|
|
45
|
-
"dependencies": {
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"cors": "^2.8.5",
|
|
51
|
-
"cross-env": "^7.0.3",
|
|
52
|
-
"dotenv": "^16.3.1",
|
|
53
|
-
"express": "^4.18.2",
|
|
54
|
-
"express-pino-logger": "^7.0.0",
|
|
55
|
-
"express-validator": "^7.0.1",
|
|
56
|
-
"helmet": "^7.0.0",
|
|
57
|
-
"http-status": "^1.6.2",
|
|
58
|
-
"jsonwebtoken": "^9.0.0",
|
|
59
|
-
"lodash-es": "^4.17.21",
|
|
60
|
-
"morgan": "^1.10.0",
|
|
61
|
-
"pdf-lib": "^1.17.1",
|
|
62
|
-
"pino-http": "^8.3.3",
|
|
63
|
-
"pino-pretty": "^10.0.0",
|
|
64
|
-
"rimraf": "^5.0.1",
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"@
|
|
69
|
-
"@
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"@
|
|
74
|
-
"@
|
|
75
|
-
"@types/
|
|
76
|
-
"@types/
|
|
77
|
-
"@types/lodash-es": "^4.17.7",
|
|
78
|
-
"@types/
|
|
79
|
-
"@types/
|
|
80
|
-
"@
|
|
81
|
-
"@
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"eslint": "^
|
|
85
|
-
"eslint-
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
"
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
"node": ">= 18.0.0"
|
|
104
|
-
}
|
|
105
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "page2pdf_server",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "文书打印转换器",
|
|
5
|
+
"private": false,
|
|
6
|
+
"authors": "herzhang",
|
|
7
|
+
"repository": "https://gitee.com/heerzhang/page2pdf-server",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"yarn": "yarn",
|
|
11
|
+
"yarn命令行": "yarn add nodemon -D",
|
|
12
|
+
"注意2": "运行prettify来修复代码,免去到处是标记红色的,太严格了",
|
|
13
|
+
"prettify": "prettier --write \"src/**/*.{ts,js,json}\" \"__tests__/**/*.{ts,js,json}\"",
|
|
14
|
+
"start": "NODE_ENV=development run-s prettify lint & nodemon",
|
|
15
|
+
"build": "cross-env NODE_ENV=production run-s prettify clean transpile",
|
|
16
|
+
"lint": "eslint 'src/**/*.{ts,js}' '__tests__/**/*.{ts,js}'",
|
|
17
|
+
"lint:fix": "eslint --fix 'src/**/*.{ts,js}' '__tests__/**/*.{ts,js}' --quiet",
|
|
18
|
+
"有些隐藏错误": "需要运行watch才发现",
|
|
19
|
+
"watch": "tsc --watch",
|
|
20
|
+
"clean": "rimraf dist",
|
|
21
|
+
"test": "cross-env NODE_ENV=test jest --coverage --runInBand --detectOpenHandles",
|
|
22
|
+
"单步调试": "不必运行test,直接到__test__目录文件直接挑选修改测试项并调试",
|
|
23
|
+
"test:watchAll": "cross-env NODE_ENV=test jest --watchAll --runInBand --detectOpenHandles",
|
|
24
|
+
"transpile": "tsc",
|
|
25
|
+
"service:start": "pm2 start ecosystem.config.js",
|
|
26
|
+
"service:reload": "pm2 reload ecosystem.config.js",
|
|
27
|
+
"service:startup": "pm2 startup",
|
|
28
|
+
"service:stop": "pm2 stop ecosystem.config.js",
|
|
29
|
+
"service:list": "pm2 list ecosystem.config.js",
|
|
30
|
+
"service:delete": "pm2 delete ecosystem.config.js",
|
|
31
|
+
"service:logs": "pm2 logs",
|
|
32
|
+
"hook主动运行": "操作系统缘故,将单引号改成双引号,加转义符,提交代码会自动执行;git提交给它勾掉吧,yarn会自动运行prepare",
|
|
33
|
+
"prepare": "husky install"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"dynamic page pdf generator",
|
|
37
|
+
"html to pdf",
|
|
38
|
+
"app url to pdf",
|
|
39
|
+
"pdf generation",
|
|
40
|
+
"node.js",
|
|
41
|
+
"express",
|
|
42
|
+
"print",
|
|
43
|
+
"server"
|
|
44
|
+
],
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"chrome-remote-interface": "^0.32.2",
|
|
47
|
+
"config": "^3.3.9",
|
|
48
|
+
"connect-timeout": "^1.9.0",
|
|
49
|
+
"@types/cors": "^2.8.13",
|
|
50
|
+
"cors": "^2.8.5",
|
|
51
|
+
"cross-env": "^7.0.3",
|
|
52
|
+
"dotenv": "^16.3.1",
|
|
53
|
+
"express": "^4.18.2",
|
|
54
|
+
"express-pino-logger": "^7.0.0",
|
|
55
|
+
"express-validator": "^7.0.1",
|
|
56
|
+
"helmet": "^7.0.0",
|
|
57
|
+
"http-status": "^1.6.2",
|
|
58
|
+
"jsonwebtoken": "^9.0.0",
|
|
59
|
+
"lodash-es": "^4.17.21",
|
|
60
|
+
"morgan": "^1.10.0",
|
|
61
|
+
"pdf-lib": "^1.17.1",
|
|
62
|
+
"pino-http": "^8.3.3",
|
|
63
|
+
"pino-pretty": "^10.0.0",
|
|
64
|
+
"rimraf": "^5.0.1",
|
|
65
|
+
"@types/node": "^20.3.1",
|
|
66
|
+
"@types/jsonwebtoken": "^9.0.2",
|
|
67
|
+
"@types/express": "^4.17.17",
|
|
68
|
+
"@types/express-pino-logger": "^4.0.3",
|
|
69
|
+
"@types/bcrypt": "^5.0.0",
|
|
70
|
+
"save-dev": "^0.0.1-security"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@babel/preset-env": "^7.22.5",
|
|
74
|
+
"@babel/preset-typescript": "^7.22.5",
|
|
75
|
+
"@types/connect-timeout": "^0.0.37",
|
|
76
|
+
"@types/jest": "^29.5.2",
|
|
77
|
+
"@types/lodash-es": "^4.17.7",
|
|
78
|
+
"@types/morgan": "^1.9.4",
|
|
79
|
+
"@types/supertest": "^2.0.12",
|
|
80
|
+
"@typescript-eslint/eslint-plugin": "^5.59.11",
|
|
81
|
+
"@typescript-eslint/parser": "^5.59.11",
|
|
82
|
+
"eslint": "^8.43.0",
|
|
83
|
+
"eslint-config-prettier": "^8.8.0",
|
|
84
|
+
"eslint-plugin-import": "^2.27.5",
|
|
85
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
86
|
+
"husky": "^8.0.3",
|
|
87
|
+
"jest": "^29.5.0",
|
|
88
|
+
"jest-watch-typeahead": "^2.2.2",
|
|
89
|
+
"nodemon": "^3.0.1",
|
|
90
|
+
"npm-run-all": "^4.1.5",
|
|
91
|
+
"pm2": "^5.3.0",
|
|
92
|
+
"prettier": "^2.8.8",
|
|
93
|
+
"supertest": "^6.3.3",
|
|
94
|
+
"ts-jest": "^29.1.0",
|
|
95
|
+
"ts-node": "^10.9.1",
|
|
96
|
+
"tsconfig-paths": "^4.2.0",
|
|
97
|
+
"typescript": "^5.1.6"
|
|
98
|
+
},
|
|
99
|
+
"engines": {
|
|
100
|
+
"node": ">= 20.0.0"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -54,3 +54,72 @@ Chrome浏览器已经 可支持的有这些:
|
|
|
54
54
|
CDP({})前后两次CDP打开新的Page若前面没有close(),那么旧Page.printToPDF也会被后面新Page.navigate所覆盖的,浏览器不支持合并打印的!
|
|
55
55
|
Page.printToPDF({})实际也只能类似Chrome打印预览菜单一致,只能打印当前页面,动态内容页面也是随着鼠标用户操作有可能不一样的打印结果。
|
|
56
56
|
|
|
57
|
+
【精准控制打印位置思路】
|
|
58
|
+
简单直接做法<span position: absolute; bottom: -1200vh;">尾巴</span>但须知道整个文档打印几张纸的,然后在作为参数优化css的计算再来决定做控制,可能会有重叠paint需强制断开页。
|
|
59
|
+
中间区域<div style="position: relative; page-break-before: always;">¥..<span style="position: absolute;top: calc(200vh - 3rem);">尾巴</span>
|
|
60
|
+
必须知道该区域有几张纸;可能会有重叠paint需强制断开页。
|
|
61
|
+
浮动布局<div style="display: flex; flex-direction: column; align-content: flex-end; height: 1300vh;"><div height: 100%;>¥</div><div display: inline-flex;>尾巴</div>必须知道该区域有几张纸,不用担心会有重叠paint。
|
|
62
|
+
后面一定有强制页断开的情景,把紧邻前面的元素定位到纸张的底部位置?【关键】是强制分页能确保独立性。
|
|
63
|
+
<div css={{"@media print": {pageBreakBefore: 'always'}}}><span style="position: absolute;top: -3rem;">尾巴</span><Text>紧跟的新页</Text></div>不需要算区域有几张,不用担心会有重叠paint。
|
|
64
|
+
最后一页情况乱特例<div style="height: 0vh; position: relative; page-break-before: always;"><span style="display: block; position:absolute; bottom:0;">尾巴</span></div>最后多个尾随的空白页!多出一个空白页情况的不打印?
|
|
65
|
+
2023/11/23:验证这下的代码还是无法正常打印页码 位置的。每一个重复div标签计数一次,只显示一次。
|
|
66
|
+
<div css={{"@media print": {pageBreakBefore: 'always'}}}>
|
|
67
|
+
<div
|
|
68
|
+
css={{
|
|
69
|
+
position: 'absolute',
|
|
70
|
+
top: '-2rem',
|
|
71
|
+
"@media print": {
|
|
72
|
+
":after": {
|
|
73
|
+
content: 'counter(page)',
|
|
74
|
+
},
|
|
75
|
+
counterIncrement: 'page',
|
|
76
|
+
// position: 'absolute',
|
|
77
|
+
// bottom: '8rem',
|
|
78
|
+
right: '-1rem',
|
|
79
|
+
fontSize: '12px',
|
|
80
|
+
color: '#777',
|
|
81
|
+
padding: '5px',
|
|
82
|
+
backgroundColor: '#f7f7f7',
|
|
83
|
+
border: '1px solid #ddd',
|
|
84
|
+
borderRadius: '2px',
|
|
85
|
+
// pageBreakAfter: 'always',
|
|
86
|
+
}
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
尾巴
|
|
90
|
+
</div>
|
|
91
|
+
<Text>紧跟的新页</Text>
|
|
92
|
+
</div>
|
|
93
|
+
【动态内容遇到的打印问题】末尾插入方式“只能支持页面启动就明确打印页数的才有效;用副作用中间纯动态变更的就不行了,打印页数早就被限制死了,后面无法再改了。
|
|
94
|
+
const [vtpage, setVtpage] = React.useState(Math.ceil((yBdv0?.top!-yHolw?.bottom!)/yHolw?.width!) || 0);
|
|
95
|
+
React.useEffect(() => {
|
|
96
|
+
if((yBdv0?.top!-yHolw?.bottom!)>0 && (yHolw?.width!)<yBdv0?.width! && (yHolw?.bottom!)>0)
|
|
97
|
+
{
|
|
98
|
+
console.log(`撞见该是却也显示了情形:`, yHolw,"虚拟断开",yBdv0, "系数有=", (yBdv0?.top!-yHolw?.bottom!)/yHolw?.width!);
|
|
99
|
+
//没处理【报错】Too many re-renders. React limits the number of renders to prevent an infinite loop.
|
|
100
|
+
setVtpage(Math.ceil((yBdv0?.top!-yHolw?.bottom!)/yHolw?.width!) || 0);
|
|
101
|
+
}
|
|
102
|
+
}, [yBdv0, yHolw, setVtpage]);
|
|
103
|
+
return <div ref={refBdv0} css={{"@media print": {pageBreakBefore:'always'}}}/>
|
|
104
|
+
{ (new Array(vtpage)).fill(null).map((_, i:number) => {
|
|
105
|
+
console.log(`该是却也显示了情形加虚拟纸张数=`, vtpage,"断页i=",i);
|
|
106
|
+
return <div key={i} css={{"@media print": {pageBreakBefore:'always'}}}/>;
|
|
107
|
+
}) }
|
|
108
|
+
foot: [
|
|
109
|
+
'<div style=\\"position: relative; width: 100%; text-align: left; margin: 10px 0px 1.5mm; font-size: 8pt;\\">',
|
|
110
|
+
'<div style=\\"position: absolute; text-align: left; top: 5px;left: 60px;\\"></div>',
|
|
111
|
+
'<div style=\\"position: absolute; width: 100%; text-align: center; top: 5px;\\"></div>',
|
|
112
|
+
'<div style=\\"-NOT_DISPLAY- position: absolute; text-align: right;top: 5px;right: 20px;\\">共 <span>~totalPages~</span> 页 / 第 <span>~pageNumber~</span> 页</div>',
|
|
113
|
+
"</div>",
|
|
114
|
+
],
|
|
115
|
+
不同版本的Chrome可能对CSS样式、字体渲染或布局引擎存在差异,导致生成的PDF不一致。若网页使用的字体在目标电脑上未安装,Chrome可能自动替换为默认字体,网络延迟资源加载失败,所以用Puppeteer替代浏览器原生打印。
|
|
116
|
+
|
|
117
|
+
失败,Error: 开启窗口尝试2次失败: 【因为】浏览器启动参数要配置特定端口号的。
|
|
118
|
+
|
|
119
|
+
[功能按钮]
|
|
120
|
+
打印预览{非检验员也能用},
|
|
121
|
+
本地转Pdf{非检验员也能用,未盖章,安装node等},
|
|
122
|
+
后端转Pdf{未盖章,立刻下载,用户需排队},
|
|
123
|
+
上传Pdf盖章,
|
|
124
|
+
代理转Pdf{异步的,不能提前获得结果,独立服务器},
|
|
125
|
+
下载盖章后Pdf{假如有存储Pdf};
|
package/src/app.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
1
2
|
import cors from "cors";
|
|
2
3
|
import express from "express";
|
|
3
4
|
import helmet from "helmet";
|
|
@@ -5,8 +6,12 @@ import morgan from "morgan";
|
|
|
5
6
|
import timeout from "connect-timeout";
|
|
6
7
|
import CONFIG from "./configEnv";
|
|
7
8
|
import { expressPinoLogger } from "./helpers";
|
|
8
|
-
import * as errorHandler from "
|
|
9
|
-
import routes from "
|
|
9
|
+
import * as errorHandler from "./middlewares/errorHandler";
|
|
10
|
+
import routes from "./routes";
|
|
11
|
+
|
|
12
|
+
/**打印不全,太多页数情形:
|
|
13
|
+
* */
|
|
14
|
+
EventEmitter.defaultMaxListeners = 100;
|
|
10
15
|
|
|
11
16
|
export const createApp = (): express.Application => {
|
|
12
17
|
const app = express();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OK } from "http-status/lib";
|
|
2
2
|
import { HomeServices } from "./services";
|
|
3
|
-
import { getAppInfoQuery } from "
|
|
4
|
-
import { apiResponse } from "
|
|
3
|
+
import { getAppInfoQuery } from "../../types/request/home";
|
|
4
|
+
import { apiResponse } from "../../helpers/apiResponse";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* 为何为了保证单步模式调试运行"test:watchAll": "cross-env NODE_ENV=development jest --watchAll --runInBand --detectOpenHandles",
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { OK } from "http-status/lib";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import {
|
|
3
|
+
ConfigRoot,
|
|
4
|
+
FileTransform,
|
|
5
|
+
HeadFooter,
|
|
6
|
+
} from "../../types/request/config";
|
|
7
|
+
import { apiResponse } from "../../helpers/apiResponse";
|
|
8
|
+
import { RenderPDF } from "../../utils/pdfgen";
|
|
9
|
+
import CONFIG from "../../configEnv";
|
|
6
10
|
|
|
7
11
|
export class PdfController {
|
|
8
12
|
/** node.js对比前端App 太严格了,一点小毛病页不行啊。
|
|
@@ -14,7 +18,7 @@ export class PdfController {
|
|
|
14
18
|
*/
|
|
15
19
|
static postMakePdf = async (req: Req, res: Res, next: NextFn) => {
|
|
16
20
|
try {
|
|
17
|
-
// const appInfoKey1 = req.query.key as getAppInfoQuery;
|
|
21
|
+
// const appInfoKey1 = req.query.key as getAppInfoQuery; 所有打印都走ConfigRoot配置包=task:可能有多个URL合并输出的。
|
|
18
22
|
const task = req.body as ConfigRoot<FileTransform>;
|
|
19
23
|
task.lay as HeadFooter;
|
|
20
24
|
//预处理汇总输出的pdf页眉页脚, 转换页眉页脚的html存储
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import AppInformation from "
|
|
2
|
-
import { HomeDAO } from "
|
|
3
|
-
import { getAppInfoQuery } from "
|
|
1
|
+
import AppInformation from "../../types/response/AppInformation";
|
|
2
|
+
import { HomeDAO } from "../../db/home";
|
|
3
|
+
import { getAppInfoQuery } from "../../types/request/home";
|
|
4
4
|
|
|
5
5
|
export class HomeServices {
|
|
6
6
|
homeDAO!: HomeDAO;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import { OK } from "http-status/lib";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
ConfigRoot,
|
|
5
|
+
FileTransform,
|
|
6
|
+
HeadFooter,
|
|
7
|
+
} from "../../types/request/config";
|
|
8
|
+
import { apiResponse } from "../../helpers/apiResponse";
|
|
5
9
|
|
|
6
|
-
import { RenderPDF } from "
|
|
7
|
-
import CONFIG from "
|
|
8
|
-
import { SplitConfig, SplitPdfFile } from "
|
|
10
|
+
import { RenderPDF } from "../../utils/pdfgen";
|
|
11
|
+
import CONFIG from "../../configEnv";
|
|
12
|
+
import { SplitConfig, SplitPdfFile } from "../../types/request/split";
|
|
9
13
|
// import CONFIG from "@/configEnv";
|
|
10
14
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
11
15
|
const { PDFDocument } = require("pdf-lib");
|
package/src/db/home.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import AppInformation from "
|
|
2
|
-
import CONFIG from "
|
|
3
|
-
import { getAppInfoQuery } from "
|
|
1
|
+
import AppInformation from "../types/response/AppInformation";
|
|
2
|
+
import CONFIG from "../configEnv";
|
|
3
|
+
import { getAppInfoQuery } from "../types/request/home";
|
|
4
4
|
|
|
5
5
|
export class HomeDAO {
|
|
6
6
|
get = (key?: getAppInfoQuery): Promise<AppInformation | any> => {
|
package/src/helpers/loggers.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// ! Don't convert require into import
|
|
2
2
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
3
|
-
|
|
3
|
+
//去掉这个了 离开开发环境无法 独立运行服务端,报错module-alias找不到
|
|
4
|
+
//去掉这个了 require("module-alias").addAlias("@", __dirname);
|
|
5
|
+
//去掉这个了
|
|
4
6
|
|
|
5
7
|
import { createApp } from "./app";
|
|
6
8
|
import { startServer } from "./server";
|
|
@@ -11,3 +13,4 @@ if (process.env.NODE_ENV !== "test") {
|
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
//关键文档Chrome DevTools Protocol资料, https://chromedevtools.github.io/devtools-protocol/tot/Page
|
|
16
|
+
export * from "./types/request/config";
|
|
@@ -5,7 +5,7 @@ import HttpStatus, {
|
|
|
5
5
|
INTERNAL_SERVER_ERROR,
|
|
6
6
|
REQUEST_TIMEOUT,
|
|
7
7
|
} from "http-status/lib";
|
|
8
|
-
import { TimeOutError } from "
|
|
8
|
+
import { TimeOutError } from "../helpers/error";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @description Error response middleware for 404 not found. This middleware function should be at the very bottom of the stack.
|
package/src/routes/index.ts
CHANGED
|
@@ -86,6 +86,9 @@ export interface FileTransform {
|
|
|
86
86
|
footerTemplate?: string;
|
|
87
87
|
headerTemplateL?: string;
|
|
88
88
|
footerTemplateL?: string;
|
|
89
|
+
//这针对:可能由于动态布局导致的浏览器打印预览和实际URL纯粹js网页动态布局的真实内容页数不一致的毛病。
|
|
90
|
+
//开启的浏览器窗口宽度。默认最大化的。
|
|
91
|
+
brwidth?: number;
|
|
89
92
|
//第一阶段Url转pdf的浏览器执行窗口的targetId,不可配置。
|
|
90
93
|
CDPtab?: string;
|
|
91
94
|
}
|
|
@@ -99,7 +102,8 @@ export interface FileTransform {
|
|
|
99
102
|
For example, <span class=title></span> would generate span containing the title.
|
|
100
103
|
*/
|
|
101
104
|
|
|
102
|
-
/**页眉页脚声明;前提条件是页眉页脚htm
|
|
105
|
+
/**页眉页脚声明;前提条件是页眉页脚htm描述里面: 上一个版本保留字 ${pageNumber} ${totalPages}
|
|
106
|
+
* 不应该把 -NOT_DISPLAY- ~pageNumber~ ~totalPages~ 这三个 保留字字符串当作普通文字输出的。
|
|
103
107
|
* 前端发送数据包时刻,页眉页脚的脚本需要在 HTML转JSON 工具网站 进行转换处理后的;网站地址 https://uutool.cn/html2json/ 转换htm模板后是数组[string]
|
|
104
108
|
* 实际上也就进行拆分多行字符串拼接数组,还有把html语法中的 ""引号 进行转义 style="position: 变成 style=\\\"position: 处置的。
|
|
105
109
|
* */
|
|
@@ -112,10 +116,11 @@ export interface HeadFooter {
|
|
|
112
116
|
//双面打印:右手边位置的,或单面打印使用
|
|
113
117
|
foot?: string | string[] | null; //实际前端发过来却是[string]的也可以啊。
|
|
114
118
|
/**可注入3个参数"特殊标记字符串"如下: 服务端依据参数来对注入的html进行修改的。
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* -NOT_DISPLAY-
|
|
118
|
-
* 这三个分别代表 页码 总页数 以及页码<div style=""></div
|
|
119
|
+
* ~pageNumber~
|
|
120
|
+
* ~totalPages~
|
|
121
|
+
* -NOT_DISPLAY- 有页码才能显示,无页码就隐藏的标记。
|
|
122
|
+
* 这三个分别代表 页码 总页数 以及页码<div style=""></div>中的可替代占位位置。
|
|
123
|
+
* 其它2个替换成数字号码。
|
|
119
124
|
* */
|
|
120
125
|
//双面打印:左手边位置的
|
|
121
126
|
headL?: string | string[] | null;
|
package/src/utils/crypt.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import bcrypt from "bcrypt";
|
|
2
|
-
import CONFIG from "@/configEnv";
|
|
1
|
+
// import bcrypt from "bcrypt";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* @description Create a bcrypt hash for a string.
|
|
6
5
|
* @param {string} value
|
|
7
6
|
* @returns {Promise<any>}
|
|
8
7
|
*/
|
|
9
|
-
export const hash = async (value: string): Promise<any> => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
8
|
+
// export const hash = async (value: string): Promise<any> => {
|
|
9
|
+
// const saltRounds = parseInt(CONFIG.AUTH.SALT_ROUNDS, 10);
|
|
10
|
+
//
|
|
11
|
+
// return bcrypt.hash(value, saltRounds);
|
|
12
|
+
// };
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* @description Compare a string with the hash.
|
|
@@ -18,9 +17,9 @@ export const hash = async (value: string): Promise<any> => {
|
|
|
18
17
|
* @param {string} hashedValue
|
|
19
18
|
* @returns {Promise<boolean>}
|
|
20
19
|
*/
|
|
21
|
-
export const compare = async (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
): Promise<boolean> => {
|
|
25
|
-
|
|
26
|
-
};
|
|
20
|
+
// export const compare = async (
|
|
21
|
+
// value: string,
|
|
22
|
+
// hashedValue: string,
|
|
23
|
+
// ): Promise<boolean> => {
|
|
24
|
+
// return bcrypt.compare(value, hashedValue);
|
|
25
|
+
// };
|
package/src/utils/pdfgen.ts
CHANGED
|
@@ -5,9 +5,13 @@ import CDP from "chrome-remote-interface";
|
|
|
5
5
|
import _ from "lodash";
|
|
6
6
|
// @ts-ignore
|
|
7
7
|
import config from "config";
|
|
8
|
-
import CONFIG from "
|
|
9
|
-
import {
|
|
10
|
-
|
|
8
|
+
import CONFIG from "../configEnv";
|
|
9
|
+
import {
|
|
10
|
+
ConfigRoot,
|
|
11
|
+
FileTransform,
|
|
12
|
+
MyPaperSize,
|
|
13
|
+
} from "../types/request/config";
|
|
14
|
+
import { filePathToUrl } from "../utils/url";
|
|
11
15
|
|
|
12
16
|
//自定义纸张系列的:
|
|
13
17
|
const paperSZs = config.get("size") as MyPaperSize[];
|
|
@@ -78,6 +82,7 @@ interface IRenderPdfOptions {
|
|
|
78
82
|
landscape?: boolean;
|
|
79
83
|
includeBackground?: boolean;
|
|
80
84
|
windowSize?: boolean;
|
|
85
|
+
//纸张 英寸为单位的。 限制打印输出的用
|
|
81
86
|
paperWidth?: number;
|
|
82
87
|
paperHeight?: number;
|
|
83
88
|
pageRanges?: string;
|
|
@@ -92,6 +97,7 @@ interface IRenderPdfOptions {
|
|
|
92
97
|
marginBottom?: number;
|
|
93
98
|
marginLeft?: number;
|
|
94
99
|
marginRight?: number;
|
|
100
|
+
//有些 要打印 背景图片的需要?
|
|
95
101
|
printBackground?: boolean;
|
|
96
102
|
}
|
|
97
103
|
|
|
@@ -124,6 +130,7 @@ export class RenderPDF {
|
|
|
124
130
|
* 奇数页还是偶数页的,双面打印对页码位置可能会调整位置的,而单独一个URL生成pdf时刻有可能没法就一定能够正确打印页码文字的是定位位置啊。抛弃支持:纯静态html横竖方向混合的场景,独立转换抽取部分纸张到过渡的pdf或拆解组合来做。
|
|
125
131
|
* 还是考虑:保留第一阶段页眉页脚的打印能力,但是仅仅针对:特殊url:file[url:{ 只能是独立的页码编码和页码合计数独自计数,不涉及到其它的URL的 }],双面打印:页码只能放在中间位置为好,页码必须从第一页开始编码的,而且这种情况目标是不采用叠加页眉页脚两次pdf方案(一次性,不提取出CSS纸张方向)。
|
|
126
132
|
* 最终采用引入reverseRanges参数设置部分纸张反转纸张方向,需要配置某某页并和CSS打印第一阶段生成的pdf能够吻合纸张方向的话,就能确保页眉页脚叠加正常位置。这里步骤就免于处理页眉页脚的生成了。
|
|
133
|
+
* CDP【Target】才是开启URL的。浏览器初始化; Page是打印输出的。
|
|
127
134
|
* */
|
|
128
135
|
async renderPdf(task: ConfigRoot<FileTransform>, tsobj: FileTransform) {
|
|
129
136
|
let options = {} as IRenderPdfOptions;
|
|
@@ -139,6 +146,7 @@ export class RenderPDF {
|
|
|
139
146
|
const { CDPclient, targetId } = await this.newTabOrPage(
|
|
140
147
|
this.getCDPhostPort(),
|
|
141
148
|
url,
|
|
149
|
+
tsobj.brwidth,
|
|
142
150
|
);
|
|
143
151
|
// await RenderPDF.newTabOrPage(this.getCDPhostPort(), url);
|
|
144
152
|
// const client = await CDP({ host: this.host, port: this.port });
|
|
@@ -343,7 +351,9 @@ export class RenderPDF {
|
|
|
343
351
|
// renderer.killChrome();
|
|
344
352
|
}
|
|
345
353
|
|
|
346
|
-
/**关键的: 某一个url的转换步骤
|
|
354
|
+
/**关键的: 某一个url的转换步骤
|
|
355
|
+
* @param tsobj 每一个独立URL生成 FileTransform 配置的
|
|
356
|
+
* */
|
|
347
357
|
async generateSinglePdf(
|
|
348
358
|
tsobj: FileTransform,
|
|
349
359
|
task: ConfigRoot<FileTransform>,
|
|
@@ -512,7 +522,9 @@ export class RenderPDF {
|
|
|
512
522
|
return ret;
|
|
513
523
|
}
|
|
514
524
|
|
|
515
|
-
/**汇总步骤入口: 前提 :前面已经生成独立的多个pdf
|
|
525
|
+
/**汇总步骤入口: 前提 :前面已经生成独立的多个pdf
|
|
526
|
+
* 另外 还有await renderer.newTabOrPage(/ renderer.yemeiyejiaoPageGen( 是用于生成页眉页脚的每一页都有独立临时pdf。
|
|
527
|
+
* */
|
|
516
528
|
static async MergeAllPdfs(
|
|
517
529
|
task: ConfigRoot<FileTransform>,
|
|
518
530
|
options: IRenderPdfOptions,
|
|
@@ -737,12 +749,12 @@ export class RenderPDF {
|
|
|
737
749
|
headerTemplate0 = headerTemplate0?.replace("-NOT_DISPLAY-", "");
|
|
738
750
|
headerTemplate0 = headerTemplate0
|
|
739
751
|
?.replace(
|
|
740
|
-
"
|
|
752
|
+
"~pageNumber~",
|
|
741
753
|
tsobj.roman && pageNumber > 0 && pageNumber < 13
|
|
742
754
|
? ROMA_NUMS[pageNumber]
|
|
743
755
|
: "" + pageNumber,
|
|
744
756
|
)
|
|
745
|
-
.replace("
|
|
757
|
+
.replace("~totalPages~", "" + totalPages);
|
|
746
758
|
}
|
|
747
759
|
options.headerTemplate = headerTemplate0;
|
|
748
760
|
let footerTemplate0;
|
|
@@ -769,12 +781,12 @@ export class RenderPDF {
|
|
|
769
781
|
footerTemplate0 = footerTemplate0?.replace("-NOT_DISPLAY-", "");
|
|
770
782
|
footerTemplate0 = footerTemplate0
|
|
771
783
|
?.replace(
|
|
772
|
-
"
|
|
784
|
+
"~pageNumber~",
|
|
773
785
|
tsobj.roman && pageNumber > 0 && pageNumber < 13
|
|
774
786
|
? ROMA_NUMS[pageNumber]
|
|
775
787
|
: "" + pageNumber,
|
|
776
788
|
)
|
|
777
|
-
.replace("
|
|
789
|
+
.replace("~totalPages~", "" + totalPages);
|
|
778
790
|
}
|
|
779
791
|
options.footerTemplate = footerTemplate0;
|
|
780
792
|
|
|
@@ -792,16 +804,17 @@ export class RenderPDF {
|
|
|
792
804
|
return null;
|
|
793
805
|
}
|
|
794
806
|
|
|
795
|
-
|
|
796
|
-
* 问题:刚用createTarget立刻获取targetId(frameId)没法正常工作,
|
|
797
|
-
* 这里await CDP(options)必须做两次!!:没法避免,否则干扰用户窗口,要么新Tab没有实际准备好的。
|
|
798
|
-
* */
|
|
799
|
-
async newTabOrPage(options: CDPbeginParam, url: string) {
|
|
807
|
+
async newTabPageInner(options: CDPbeginParam, url: string, brwidth?: number) {
|
|
800
808
|
const client1 = await CDP(options); // await CDP({ host: options.host, port: options.port });
|
|
801
|
-
const { Target } = client1;
|
|
809
|
+
const { Target, Browser } = client1;
|
|
810
|
+
//页眉页脚也会开动新的Tab; 没尝试出来:用这个入口就可控制浏览器窗口的宽度的办法。
|
|
802
811
|
await Target.createTarget({
|
|
803
812
|
url: url,
|
|
804
813
|
background: true,
|
|
814
|
+
width: 719, //无效:(headless chrome only)不算无头模式的,有展示交互式窗口啊。
|
|
815
|
+
newWindow: true,
|
|
816
|
+
// enableBeginFrameControl: true,
|
|
817
|
+
forTab: true,
|
|
805
818
|
});
|
|
806
819
|
//前端编码之escape、encodeURI对的, 和 encodeURIComponent
|
|
807
820
|
const { targetInfos } = await Target.getTargets();
|
|
@@ -809,12 +822,49 @@ export class RenderPDF {
|
|
|
809
822
|
//"type": "webview", iframe, background_page, ...
|
|
810
823
|
return tab.type === "page" && tab.url === url;
|
|
811
824
|
});
|
|
812
|
-
|
|
825
|
+
//targetId就是目标窗口的实例。
|
|
813
826
|
if (!target.targetId) throw new Error("没开启窗口:" + url);
|
|
827
|
+
if (brwidth) {
|
|
828
|
+
//需要约束浏览器 来避免打印页数不正常的,web页面的动态布局导致的,打印结果太少太多页数。
|
|
829
|
+
const { windowId, bounds } = await Browser.getWindowForTarget({
|
|
830
|
+
targetId: target.targetId,
|
|
831
|
+
});
|
|
832
|
+
const objectWidth = brwidth;
|
|
833
|
+
const newbounds = {
|
|
834
|
+
...bounds,
|
|
835
|
+
width: objectWidth,
|
|
836
|
+
windowState: "normal",
|
|
837
|
+
};
|
|
838
|
+
await Browser.setWindowBounds({ windowId, bounds: newbounds });
|
|
839
|
+
const { bounds: realBound } = await Browser.getWindowBounds({ windowId });
|
|
840
|
+
const newRealWidth = realBound?.width;
|
|
841
|
+
if (objectWidth !== newRealWidth)
|
|
842
|
+
throw new Error("开窗口宽度不适当:" + newRealWidth);
|
|
843
|
+
}
|
|
844
|
+
client1.close(); //原先每次关闭的:
|
|
814
845
|
const client = await CDP(options);
|
|
846
|
+
|
|
815
847
|
//这些用例简易网页:frameId实际等同targetId;因没有嵌套<iframe />标签的。
|
|
816
848
|
return { CDPclient: client, targetId: target.targetId };
|
|
817
849
|
}
|
|
850
|
+
/**直接嵌入用户打开的浏览器的模式下,独立的开启新的浏览器窗口需要:
|
|
851
|
+
* 问题:刚用createTarget立刻获取targetId(frameId)没法正常工作,
|
|
852
|
+
* 这里await CDP(options)必须做两次!!:没法避免,否则干扰用户窗口,要么新Tab没有实际准备好的。
|
|
853
|
+
* 另外入口是:页眉页脚是使用纯粹 静态的 yemeiyejiaoHtml 来生成的每一页的独立页眉页脚pdf再合并的,不需要考虑浏览器宽度大小会影响到(不是动态布局的内容)。
|
|
854
|
+
* */
|
|
855
|
+
async newTabOrPage(options: CDPbeginParam, url: string, brwidth?: number) {
|
|
856
|
+
//多尝试一次, 浏览器宽度设置可能不能一次性成功的。
|
|
857
|
+
for (let i = 0; i < 2; i++) {
|
|
858
|
+
try {
|
|
859
|
+
const result = await this.newTabPageInner(options, url, brwidth);
|
|
860
|
+
return result;
|
|
861
|
+
} catch (e) {
|
|
862
|
+
// @ts-ignore
|
|
863
|
+
if (!e.message.startsWith("开窗口宽度不适当:")) console.error(e);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
throw new Error("开启窗口尝试2次失败:" + url);
|
|
867
|
+
}
|
|
818
868
|
/**假如不是当前刚刚操作的Tab的话?:
|
|
819
869
|
* */
|
|
820
870
|
async closeTabOrPage(client: any, target: string) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
工具包需要发布,工程目录:page2pdf-server/执行 npm publish 发布到npm
|
|
2
2
|
后端没有单步调试模式太不好了,原本直接start就能单步调试,后来不行了,jest来解决:
|
|
3
3
|
__test__/./generatePdf.test.ts问题:jest用Run模式正常,可是若Debug模式单步调试的,.post(`/api/pdf`).send就会提前中止!原因xxxController容不下多个接口函数?
|
|
4
4
|
/?有断点运行卡着的并且xxxController类里有两个函数的?提前中止啊;非得拆分两个文件。
|
|
@@ -171,6 +171,8 @@ headerTemplate = `<div style="position: relative; width:100%; text-align:center;
|
|
|
171
171
|
<div style="position: absolute; width:100%; text-align: center; bottom: 5px;"><span class=title></div>
|
|
172
172
|
<div style="position: absolute; text-align: right; bottom: 5px;right: 20px;">version: 1.0</div>
|
|
173
173
|
</div>`;
|
|
174
|
+
|
|
175
|
+
默认标题 '<div style=\\"position: absolute; width:100%; text-align: center; bottom: 5px;\\"><span id=\\"titlespan\\" class=title></div>',
|
|
174
176
|
使用chrome, 不能以root账号直接使用 https://www.cnblogs.com/kk1893/p/14985512.html
|
|
175
177
|
let browserOptions = { args: ['--no-sandbox', '--disable-setuid-sandbox'] };
|
|
176
178
|
物理打印机系统对话框设置:可能能配置装订线宽度和页码输出的{套中套}可分拆却就不能文件合并。
|
|
@@ -239,4 +241,28 @@ package.json:: start 又能使用单步调试模式运行了,奇怪,为何
|
|
|
239
241
|
"canAccessOpener": false,
|
|
240
242
|
"browserContextId": "6462BDF4C120FD15A2C462621D55535F"
|
|
241
243
|
}
|
|
242
|
-
BD1CE5AD1D724197A177AC4E50DDB4A6
|
|
244
|
+
BD1CE5AD1D724197A177AC4E50DDB4A6
|
|
245
|
+
|
|
246
|
+
去掉bcrypt包 @mapbox/node-pre-gyp node-gyp 依賴python啊
|
|
247
|
+
useMeasure(dynctRef0)?.height 所获得尺寸实际针对屏幕的,在打印机场景该取值还是来自屏幕的,并非纸张打印语义的尺寸;没法测量打印时刻的div高度?。
|
|
248
|
+
|
|
249
|
+
新电脑,不是管理员账户跑的开发环境,POST http://localhost:9389/api/pdf 出现error: Error: connect ECONNREFUSED 127.0.0.1:9872
|
|
250
|
+
只好另外管理员用户启动浏览器窗口。当前打印服务器的为何却要调用管理员账户启动的浏览器?不能直接用当前用户启动浏览器,非要windows管理员窗口的。
|
|
251
|
+
奇怪浏览器手动去打印必须勾选页眉页脚才正常,没选的表格最右边会不见了,但是打印服务器不受此困扰,两者看似能确保页数一致性的。
|
|
252
|
+
去掉了: 没法在生产版本中用,没包报错
|
|
253
|
+
"devDependencies": {
|
|
254
|
+
"@types/module-alias": "^2.0.1",
|
|
255
|
+
"module-alias": "^2.2.3",
|
|
256
|
+
}
|
|
257
|
+
不是module-alias的问题,而是node_modules在生产目录底下也不能缺少啊!直接拷贝也行! https://blog.csdn.net/qq_35624642/article/details/128960311
|
|
258
|
+
windows用户若非管理员账户的:桌面上的Chrome快捷方式右键点属性在“兼容性”设置中:必须勾选“管理员身份运行”!否则端口remote-debugging-port没开启。
|
|
259
|
+
【浏览器有BUG】
|
|
260
|
+
预览打印的,若没有勾选页眉页脚的话可能导致表格输出的右边边框被裁剪看不见表格右边线了,就只能勾选页眉页脚,但是我这个打印转换服务器没有这个毛病。
|
|
261
|
+
但问题是:勾不勾选页眉页脚会不会影响到了实际打印输出的页数啊?目前还未发现这个影响存在的。
|
|
262
|
+
如何限制打印时刻浏览器宽的:
|
|
263
|
+
入口代码:src/utils/pdfgen.ts 的 async renderPdf(task: ConfigRoot<FileTransform>, tsobj: FileTransform) 浏览器打印主入口:
|
|
264
|
+
【CDP文档】 https://chromedevtools.github.io/devtools-protocol/tot/Target/
|
|
265
|
+
首先必须手动开启chrome浏览器;而且这个浏览器窗体宽度直接影响打印输出!可能会漏打,动态div测量调整布局问题。影响到浏览器其它用途体验。
|
|
266
|
+
yarn又失败: 无法访问上 https://registry.npmjs.org/
|
|
267
|
+
问题:有些URL网页,一直在等待加载好,死等打印命令没结果,估计CDP依据页面的网络消息决定加载结束与否的。
|
|
268
|
+
端口已经被占用,启动不能访问预定端口的:查进程 netstat -ano | findstr :9872 #找出 LISTENING 10972 =进程ID;杀掉
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
POST http://localhost:9389/api/pdf
|
|
2
|
+
Accept: application/json
|
|
3
|
+
Content-Type: application/json; charset=utf-8
|
|
4
|
+
|
|
5
|
+
{
|
|
6
|
+
"merge": true,
|
|
7
|
+
"name": "asjsak啊实打实2",
|
|
8
|
+
"lay": {
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"url": "http://192.168.1.102:3765/originalView/CR-JJ/ver/1/-3kfCWcBRx66P8YA7EOkXFJlcG9ydA/printAll",
|
|
13
|
+
"out": "横页脚的VS2"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
###
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
<component name="ProjectCodeStyleConfiguration">
|
|
2
|
-
<code_scheme name="Project" version="173">
|
|
3
|
-
<HTMLCodeStyleSettings>
|
|
4
|
-
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
|
5
|
-
<option name="HTML_ENFORCE_QUOTES" value="true" />
|
|
6
|
-
</HTMLCodeStyleSettings>
|
|
7
|
-
<JSCodeStyleSettings version="0">
|
|
8
|
-
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
|
9
|
-
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
|
10
|
-
<option name="FORCE_QUOTE_STYlE" value="true" />
|
|
11
|
-
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
|
12
|
-
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
|
13
|
-
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
|
14
|
-
</JSCodeStyleSettings>
|
|
15
|
-
<TypeScriptCodeStyleSettings version="0">
|
|
16
|
-
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
|
17
|
-
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
|
18
|
-
<option name="FORCE_QUOTE_STYlE" value="true" />
|
|
19
|
-
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
|
20
|
-
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
|
21
|
-
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
|
22
|
-
</TypeScriptCodeStyleSettings>
|
|
23
|
-
<VueCodeStyleSettings>
|
|
24
|
-
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
|
25
|
-
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
|
26
|
-
</VueCodeStyleSettings>
|
|
27
|
-
<codeStyleSettings language="HTML">
|
|
28
|
-
<option name="SOFT_MARGINS" value="80" />
|
|
29
|
-
<indentOptions>
|
|
30
|
-
<option name="INDENT_SIZE" value="2" />
|
|
31
|
-
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
|
32
|
-
<option name="TAB_SIZE" value="2" />
|
|
33
|
-
</indentOptions>
|
|
34
|
-
</codeStyleSettings>
|
|
35
|
-
<codeStyleSettings language="JavaScript">
|
|
36
|
-
<option name="SOFT_MARGINS" value="80" />
|
|
37
|
-
<indentOptions>
|
|
38
|
-
<option name="INDENT_SIZE" value="2" />
|
|
39
|
-
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
|
40
|
-
<option name="TAB_SIZE" value="2" />
|
|
41
|
-
</indentOptions>
|
|
42
|
-
</codeStyleSettings>
|
|
43
|
-
<codeStyleSettings language="TypeScript">
|
|
44
|
-
<option name="SOFT_MARGINS" value="80" />
|
|
45
|
-
<indentOptions>
|
|
46
|
-
<option name="INDENT_SIZE" value="2" />
|
|
47
|
-
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
|
48
|
-
<option name="TAB_SIZE" value="2" />
|
|
49
|
-
</indentOptions>
|
|
50
|
-
</codeStyleSettings>
|
|
51
|
-
<codeStyleSettings language="Vue">
|
|
52
|
-
<option name="SOFT_MARGINS" value="80" />
|
|
53
|
-
<indentOptions>
|
|
54
|
-
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
|
55
|
-
</indentOptions>
|
|
56
|
-
</codeStyleSettings>
|
|
57
|
-
</code_scheme>
|
|
58
|
-
</component>
|
package/.idea/encodings.xml
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
<component name="InspectionProjectProfileManager">
|
|
2
|
-
<profile version="1.0">
|
|
3
|
-
<option name="myName" value="Project Default" />
|
|
4
|
-
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
5
|
-
<inspection_tool class="TsLint" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
6
|
-
</profile>
|
|
7
|
-
</component>
|
package/.idea/modules.xml
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<project version="4">
|
|
3
|
-
<component name="ProjectModuleManager">
|
|
4
|
-
<modules>
|
|
5
|
-
<module fileurl="file://$PROJECT_DIR$/.idea/tenstack-starter-main.iml" filepath="$PROJECT_DIR$/.idea/tenstack-starter-main.iml" />
|
|
6
|
-
</modules>
|
|
7
|
-
</component>
|
|
8
|
-
</project>
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<module type="WEB_MODULE" version="4">
|
|
3
|
-
<component name="NewModuleRootManager">
|
|
4
|
-
<content url="file://$MODULE_DIR$">
|
|
5
|
-
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
6
|
-
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
7
|
-
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
8
|
-
</content>
|
|
9
|
-
<orderEntry type="inheritedJdk" />
|
|
10
|
-
<orderEntry type="sourceFolder" forTests="false" />
|
|
11
|
-
</component>
|
|
12
|
-
</module>
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<module type="WEB_MODULE" version="4">
|
|
3
|
-
<component name="NewModuleRootManager">
|
|
4
|
-
<content url="file://$MODULE_DIR$">
|
|
5
|
-
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
6
|
-
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
7
|
-
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
8
|
-
</content>
|
|
9
|
-
<orderEntry type="inheritedJdk" />
|
|
10
|
-
<orderEntry type="sourceFolder" forTests="false" />
|
|
11
|
-
</component>
|
|
12
|
-
</module>
|