Skip to content

Commit 6050cc2

Browse files
committed
feat(pdf): support defining headers, footers, and other layout options
1 parent 3fb0237 commit 6050cc2

File tree

5 files changed

+315
-28
lines changed

5 files changed

+315
-28
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<script setup lang="ts">
2+
import { Info } from 'lucide-vue-next'
3+
import { onMounted } from 'vue'
4+
5+
const props = defineProps({
6+
visible: {
7+
type: Boolean,
8+
default: false,
9+
},
10+
})
11+
12+
const emit = defineEmits([`close`, `startpdf`])
13+
14+
const store = useStore()
15+
16+
function onUpdate(val: boolean) {
17+
if (!val) {
18+
store.pdfTitle = undefined
19+
emit(`close`)
20+
}
21+
}
22+
23+
onMounted(() => {
24+
25+
})
26+
</script>
27+
28+
<template>
29+
<Dialog :open="props.visible" @update:open="onUpdate">
30+
<DialogContent class="bg-card text-card-foreground h-dvh max-h-dvh w-full flex flex-col rounded-none shadow-xl sm:max-h-[80vh] sm:max-w-2xl sm:rounded-xl">
31+
<DialogHeader class="space-y-1 flex flex-col items-start">
32+
<DialogTitle>Docs<sup style="color:red">+</sup></DialogTitle>
33+
<p class="text-muted-foreground text-sm">
34+
设置 PDF 参数
35+
</p>
36+
</DialogHeader>
37+
<div class="custom-scroll mb-4 max-h-[calc(100dvh-10rem)] overflow-y-auto rounded-md p-4 pr-1 text-xs sm:max-h-none sm:text-sm">
38+
<div class="text-center">
39+
<div class="mb-5">
40+
<Label class="mb-1 flex items-center text-sm font-medium">
41+
中间页眉
42+
</Label>
43+
<Input
44+
v-model="store.currentPdfTitle"
45+
placeholder="留空则不显示"
46+
class="focus:border-gray-400 focus:ring-1 focus:ring-gray-300"
47+
/>
48+
</div>
49+
<div class="mb-5">
50+
<Label class="mb-1 flex items-center text-sm font-medium">
51+
左边页眉
52+
</Label>
53+
<Input
54+
v-model="store.topLeft"
55+
placeholder="留空则不显示"
56+
class="focus:border-gray-400 focus:ring-1 focus:ring-gray-300"
57+
/>
58+
</div>
59+
<div class="mb-5">
60+
<Label class="mb-1 flex items-center text-sm font-medium">
61+
右边页眉
62+
</Label>
63+
<Input
64+
v-model="store.topRight"
65+
placeholder="留空则不显示"
66+
class="focus:border-gray-400 focus:ring-1 focus:ring-gray-300"
67+
/>
68+
</div>
69+
<div class="mb-5">
70+
<Label class="mb-1 flex items-center text-sm font-medium">
71+
左边页脚
72+
</Label>
73+
<Input
74+
v-model.string="store.bottomLeft"
75+
placeholder="留空则不显示"
76+
class="focus:border-gray-400 focus:ring-1 focus:ring-gray-300"
77+
/>
78+
</div>
79+
<div class="mb-5">
80+
<Label class="mb-1 flex items-center text-sm font-medium">
81+
右边页脚
82+
<TooltipProvider>
83+
<Tooltip>
84+
<TooltipTrigger as-child>
85+
<Info class="text-gray-500" :size="16" />
86+
</TooltipTrigger>
87+
<TooltipContent>
88+
<div>
89+
"第 " counter(page) " 页,共 " counter(pages) " 页"
90+
<div>可以按照上面格式自己定义</div>
91+
</div>
92+
</TooltipContent>
93+
</Tooltip>
94+
</TooltipProvider>
95+
</Label>
96+
<Input
97+
v-model.string="store.bottomRight"
98+
placeholder="留空则不显示"
99+
class="focus:border-gray-400 focus:ring-1 focus:ring-gray-300"
100+
/>
101+
</div>
102+
<div class="mb-2">
103+
<Label class="mb-1 flex items-center text-sm font-medium">
104+
打印页边距
105+
<TooltipProvider>
106+
<Tooltip>
107+
<TooltipTrigger as-child>
108+
<Info class="text-gray-500" :size="16" />
109+
</TooltipTrigger>
110+
<TooltipContent>
111+
<div>
112+
20mm 20mm 表示上下边距 20mm,左右 20mm
113+
</div>
114+
</TooltipContent>
115+
</Tooltip>
116+
</TooltipProvider>
117+
</Label>
118+
<Input
119+
v-model.string="store.printMargin"
120+
placeholder="留空则无边距"
121+
class="focus:border-gray-400 focus:ring-1 focus:ring-gray-300"
122+
/>
123+
</div>
124+
</div>
125+
</div>
126+
<DialogFooter class="sm:justify-evenly">
127+
<Button
128+
variant="destructive"
129+
@click="store.resetPdfConfig()"
130+
>
131+
重置
132+
</Button>
133+
<Button
134+
@click="emit(`startpdf`)"
135+
>
136+
导出
137+
</Button>
138+
</DialogFooter>
139+
</DialogContent>
140+
</Dialog>
141+
</template>

src/components/CodemirrorEditor/EditorHeader/FileDropdown.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ const {
1616
exportEditorContent2PureHTML,
1717
exportEditorContent2MD,
1818
downloadAsCardImage,
19-
exportEditorContent2PDF,
19+
export2PDF,
2020
} = store
2121
2222
const editorStateDialogVisible = ref(false)
23+
const exportPdfDialogVisible = ref(false)
2324
2425
const importMarkdownContent = useImportMarkdownContent()
2526
</script>
@@ -46,7 +47,7 @@ const importMarkdownContent = useImportMarkdownContent()
4647
<FileCode class="mr-2 size-4" />
4748
导出 .html(无样式)
4849
</MenubarItem>
49-
<MenubarItem @click="exportEditorContent2PDF()">
50+
<MenubarItem @click="exportPdfDialogVisible = true">
5051
<FileText class="mr-2 size-4" />
5152
导出 .pdf
5253
</MenubarItem>
@@ -72,4 +73,6 @@ const importMarkdownContent = useImportMarkdownContent()
7273

7374
<!-- 各弹窗挂载 -->
7475
<EditorStateDialog :visible="editorStateDialogVisible" @close="editorStateDialogVisible = false" />
76+
77+
<ExportPdfDialog :visible="exportPdfDialogVisible" @startpdf="export2PDF(emit)" @close="exportPdfDialogVisible = false" />
7578
</template>

src/constants/PDFConfig.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const DEFAULT_PRINT_MARGIN = `25mm 20mm`
2+
export const DEFAULT_TOP_LEFT = ``
3+
export const DEFAULT_TOP_RIGHT = ``
4+
export const DEFAULT_BOTTOM_LEFT = `Docs+`
5+
export const DEFAULT_BOTTOM_RIGHT = `"第 " counter(page) " 页,共 " counter(pages) " 页"`

src/stores/index.ts

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ import {
1111
themeMap,
1212
widthOptions,
1313
} from '@/config'
14+
import {
15+
DEFAULT_BOTTOM_LEFT,
16+
DEFAULT_BOTTOM_RIGHT,
17+
DEFAULT_PRINT_MARGIN,
18+
DEFAULT_TOP_LEFT,
19+
DEFAULT_TOP_RIGHT,
20+
} from '@/constants/PDFConfig'
21+
1422
import {
1523
addPrefix,
1624
downloadFile,
@@ -21,6 +29,7 @@ import {
2129
formatDoc,
2230
sanitizeTitle,
2331
} from '@/utils'
32+
2433
import { css2json, customCssWithTemplate, customizeTheme, postProcessHtml, renderMarkdown } from '@/utils/'
2534
import { copyPlain } from '@/utils/clipboard'
2635
import copy from '@/utils/contentExporter'
@@ -661,9 +670,10 @@ export const useStore = defineStore(`store`, () => {
661670
}
662671

663672
// 导出编辑器内容为 PDF
664-
const exportEditorContent2PDF = () => {
665-
exportPDF(primaryColor.value, posts.value[currentPostIndex.value].title)
666-
document.querySelector(`#output`)!.innerHTML = output.value
673+
async function export2PDF(emit: any) {
674+
// 放入拷贝 html 的逻辑代码
675+
const fullHtml = await copy(`outhtml`, emit)
676+
exportPDF(fullHtml!)
667677
}
668678

669679
// 导出编辑器内容到本地
@@ -720,6 +730,35 @@ export const useStore = defineStore(`store`, () => {
720730
isOpenConfirmDialog.value = true
721731
}
722732

733+
const printMargin = useStorage<string>(`print_margin`, DEFAULT_PRINT_MARGIN)
734+
// const topCenter = useStorage<string>(`print_top_center`, DEFAULT_TOP_CENTER)
735+
const topLeft = useStorage<string>(`print_top_left`, DEFAULT_TOP_LEFT)
736+
const topRight = useStorage<string>(`print_top_right`, DEFAULT_TOP_RIGHT)
737+
const bottomLeft = useStorage<string>(`print_bottom_left`, DEFAULT_BOTTOM_LEFT)
738+
const bottomRight = useStorage<string>(`print_bottom_right`, DEFAULT_BOTTOM_RIGHT)
739+
740+
const pdfTitle = ref<string | undefined>(undefined)
741+
const currentPdfTitle = computed<string>({
742+
get() {
743+
const idx = currentPostIndex.value
744+
return pdfTitle.value === undefined
745+
? posts.value[idx].title
746+
: pdfTitle.value
747+
},
748+
set(val: string) {
749+
pdfTitle.value = val
750+
},
751+
})
752+
753+
function resetPdfConfig() {
754+
printMargin.value = DEFAULT_PRINT_MARGIN
755+
topLeft.value = DEFAULT_TOP_LEFT
756+
topRight.value = DEFAULT_TOP_RIGHT
757+
bottomLeft.value = DEFAULT_BOTTOM_LEFT
758+
bottomRight.value = DEFAULT_BOTTOM_RIGHT
759+
pdfTitle.value = undefined
760+
}
761+
723762
return {
724763
isDark,
725764
toggleDark,
@@ -778,7 +817,7 @@ export const useStore = defineStore(`store`, () => {
778817
export2HTML,
779818
exportEditorContent2PureHTML,
780819
exportEditorContent2MD,
781-
exportEditorContent2PDF,
820+
export2PDF,
782821
downloadAsCardImage,
783822

784823
importDefaultContent,
@@ -817,6 +856,14 @@ export const useStore = defineStore(`store`, () => {
817856
updatePostParentId,
818857
collapseAllPosts,
819858
expandAllPosts,
859+
pdfTitle,
860+
currentPdfTitle,
861+
printMargin,
862+
topLeft,
863+
topRight,
864+
bottomLeft,
865+
bottomRight,
866+
resetPdfConfig,
820867
}
821868
})
822869

0 commit comments

Comments
 (0)