|
@@ -3,6 +3,8 @@ import { ref, onMounted } from 'vue'
|
|
|
import { message } from 'ant-design-vue'
|
|
|
import type { FormInstance } from 'ant-design-vue'
|
|
|
import { useRoute } from 'vue-router'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
+import { Share, Download, Printer, ArrowUp } from '@element-plus/icons-vue'
|
|
|
|
|
|
interface CandidateInfo {
|
|
|
name: string
|
|
@@ -68,6 +70,10 @@ interface CandidateInfo {
|
|
|
images: string[]
|
|
|
}
|
|
|
}
|
|
|
+ strengths?: string[]
|
|
|
+ weaknesses?: string[]
|
|
|
+ hireRecommendation?: string
|
|
|
+ hireReason?: string
|
|
|
}
|
|
|
|
|
|
const candidateInfo = ref<CandidateInfo>({
|
|
@@ -169,7 +175,11 @@ const candidateInfo = ref<CandidateInfo>({
|
|
|
|
|
|
]
|
|
|
}
|
|
|
- }
|
|
|
+ },
|
|
|
+ strengths: [],
|
|
|
+ weaknesses: [],
|
|
|
+ hireRecommendation: '',
|
|
|
+ hireReason: ''
|
|
|
})
|
|
|
|
|
|
const apiData = ref<any>(null)
|
|
@@ -262,22 +272,41 @@ const updateCandidateInfo = (data: any) => {
|
|
|
candidateInfo.value.suggestedSalary = '面议'
|
|
|
}
|
|
|
|
|
|
- // 更新综合评分
|
|
|
- if (application?.comprehensive_score !== null && application?.comprehensive_score !== undefined) {
|
|
|
- candidateInfo.value.score = application.comprehensive_score
|
|
|
- } else {
|
|
|
- // 如果没有综合评分,计算面试题目的平均分
|
|
|
- const answeredQuestions = interview_progress?.filter((q: any) =>
|
|
|
- q.video_answer && q.video_answer.ai_score
|
|
|
- ) || []
|
|
|
+ // 更新综合分析
|
|
|
+ if (application?.comprehensive_analysis) {
|
|
|
+ // 更新综合评分
|
|
|
+ if (application.comprehensive_analysis.comprehensive_score !== null &&
|
|
|
+ application.comprehensive_analysis.comprehensive_score !== undefined) {
|
|
|
+ candidateInfo.value.score = application.comprehensive_analysis.comprehensive_score
|
|
|
+ }
|
|
|
|
|
|
- if (answeredQuestions.length > 0) {
|
|
|
- const totalScore = answeredQuestions.reduce((sum: number, q: any) =>
|
|
|
- sum + (q.video_answer.ai_score || 0), 0
|
|
|
- )
|
|
|
- candidateInfo.value.score = Math.round(totalScore / answeredQuestions.length)
|
|
|
- } else {
|
|
|
- candidateInfo.value.score = 0
|
|
|
+ // 更新优缺点分析
|
|
|
+ if (application.comprehensive_analysis.video_analysis_data) {
|
|
|
+ const videoData = application.comprehensive_analysis.video_analysis_data
|
|
|
+
|
|
|
+ // 更新优点
|
|
|
+ if (videoData.strengths && videoData.strengths.length > 0) {
|
|
|
+ candidateInfo.value.strengths = videoData.strengths.filter((s: string) =>
|
|
|
+ s && !s.includes('无法从响应中提取')
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新缺点
|
|
|
+ if (videoData.weaknesses && videoData.weaknesses.length > 0) {
|
|
|
+ candidateInfo.value.weaknesses = videoData.weaknesses.filter((w: string) =>
|
|
|
+ w && !w.includes('无法从响应中提取')
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新录用建议
|
|
|
+ if (application.comprehensive_analysis.hire_recommendation) {
|
|
|
+ candidateInfo.value.hireRecommendation = application.comprehensive_analysis.hire_recommendation
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新录用理由
|
|
|
+ if (application.comprehensive_analysis.hire_reason) {
|
|
|
+ candidateInfo.value.hireReason = application.comprehensive_analysis.hire_reason
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -404,7 +433,41 @@ const updateCandidateInfo = (data: any) => {
|
|
|
}
|
|
|
|
|
|
// 更新DUV分析
|
|
|
- if (application?.visual_analysis_results && application.visual_analysis_results.detections) {
|
|
|
+ if (application?.visual_analysis_results && application.visual_analysis_results.photo_results) {
|
|
|
+ // 从photo_results中收集所有detections
|
|
|
+ const allDetections: any[] = []
|
|
|
+
|
|
|
+ application.visual_analysis_results.photo_results.forEach((photo: any) => {
|
|
|
+ if (photo.detections && photo.detections.length > 0) {
|
|
|
+ photo.detections.forEach((detection: any) => {
|
|
|
+ allDetections.push({
|
|
|
+ title: detection.feature || '特征分析',
|
|
|
+ content: detection.location ?
|
|
|
+ `在${detection.location}发现${detection.feature}${detection.description ? ',' + detection.description : ''}` :
|
|
|
+ detection.feature + (detection.description ? ',' + detection.description : ''),
|
|
|
+ score: detection.confidence >= 0.8 ? '确认' : '疑似',
|
|
|
+ type: detection.description && detection.description.includes('影响') ? 'negative' : 'neutral'
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 如果有检测结果,更新DUV分析
|
|
|
+ if (allDetections.length > 0) {
|
|
|
+ candidateInfo.value.duvAnalysis = allDetections
|
|
|
+ } else {
|
|
|
+ // 如果没有检测结果,提供默认值
|
|
|
+ candidateInfo.value.duvAnalysis = [
|
|
|
+ {
|
|
|
+ title: '未发现特殊特征',
|
|
|
+ content: '未在照片中检测到特殊特征',
|
|
|
+ score: '正常',
|
|
|
+ type: 'positive'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ } else if (application?.visual_analysis_results && application.visual_analysis_results.detections) {
|
|
|
+ // 兼容旧版API格式
|
|
|
candidateInfo.value.duvAnalysis = application.visual_analysis_results.detections.map((detection: any) => ({
|
|
|
title: detection.feature || '特征分析',
|
|
|
content: detection.location ? `在${detection.location}发现${detection.feature}` : detection.feature,
|
|
@@ -415,15 +478,9 @@ const updateCandidateInfo = (data: any) => {
|
|
|
// 如果没有视觉分析结果,提供默认值
|
|
|
candidateInfo.value.duvAnalysis = [
|
|
|
{
|
|
|
- title: '',
|
|
|
- content: '',
|
|
|
- score: '',
|
|
|
- type: 'neutral'
|
|
|
- },
|
|
|
- {
|
|
|
- title: '',
|
|
|
- content: '',
|
|
|
- score: '',
|
|
|
+ title: '未进行DUV分析',
|
|
|
+ content: '未提供DUV分析数据',
|
|
|
+ score: '未知',
|
|
|
type: 'neutral'
|
|
|
}
|
|
|
]
|
|
@@ -512,19 +569,43 @@ const handleSubmit = async () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+const reportTop = ref<HTMLElement | null>(null)
|
|
|
+
|
|
|
const scrollToTop = () => {
|
|
|
- window.scrollTo({
|
|
|
- top: 0,
|
|
|
- behavior: 'smooth'
|
|
|
- })
|
|
|
+ console.log('尝试滚动到顶部')
|
|
|
+
|
|
|
+ // 使用ref引用直接滚动到顶部元素
|
|
|
+ if (reportTop.value) {
|
|
|
+ reportTop.value.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
|
+ console.log('使用ref滚动到顶部')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果ref不可用,尝试通过ID查找元素
|
|
|
+ const topElement = document.getElementById('report-top')
|
|
|
+ if (topElement) {
|
|
|
+ topElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
|
+ console.log('使用ID滚动到顶部')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果以上方法都失败,使用之前的备用方法
|
|
|
+ // ... 之前的代码 ...
|
|
|
}
|
|
|
|
|
|
const handleShare = () => {
|
|
|
- message.success('分享链接已复制')
|
|
|
+ ElMessage.success('分享链接已复制')
|
|
|
}
|
|
|
|
|
|
const handleDownload = () => {
|
|
|
- message.success('报告下载中...')
|
|
|
+ ElMessage.success('报告下载中...')
|
|
|
+}
|
|
|
+
|
|
|
+const handlePrint = () => {
|
|
|
+ ElMessage.success('准备打印报告...')
|
|
|
+ setTimeout(() => {
|
|
|
+ window.print()
|
|
|
+ }, 300)
|
|
|
}
|
|
|
|
|
|
// 添加base64编码的内联图片作为fallback
|
|
@@ -627,6 +708,9 @@ const handleVideoError = (event: Event) => {
|
|
|
|
|
|
<template>
|
|
|
<div class="max-w-4xl mx-auto p-6 relative overflow-y-auto h-full">
|
|
|
+ <!-- 添加顶部锚点 -->
|
|
|
+ <div id="report-top" ref="reportTop"></div>
|
|
|
+
|
|
|
<!-- 加载状态 -->
|
|
|
<a-spin :spinning="loading" tip="加载中...">
|
|
|
<!-- 页面标题 -->
|
|
@@ -694,33 +778,45 @@ const handleVideoError = (event: Event) => {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- AI维度分析 1-->
|
|
|
- <!-- <div class="mb-8">
|
|
|
- <h2 class="text-xl font-bold mb-6">1. AI维度分析</h2>
|
|
|
- <div class="space-y-6">
|
|
|
- <div v-for="(value, key) in candidateInfo.dimensions" :key="key" class="border-b pb-4">
|
|
|
- <div class="flex items-center mb-2">
|
|
|
- <span class="text-gray-600 w-32">{{ {
|
|
|
- teamwork: '团队合作能力',
|
|
|
- learningAbility: '学习能力',
|
|
|
- attention: '细致严谨',
|
|
|
- workAdaptability: '工作适应性',
|
|
|
- serviceAwareness: '服务意识'
|
|
|
- }[key] }}</span>
|
|
|
- <span class="ml-4" :class="{
|
|
|
- 'text-red-500': value === '欠佳',
|
|
|
- 'text-green-500': value === '优秀',
|
|
|
- 'text-yellow-500': value === '中等'
|
|
|
- }">{{ value }}</span>
|
|
|
+ <!-- 1. 综合评估 -->
|
|
|
+ <div class="mb-8">
|
|
|
+ <h2 class="text-xl font-bold mb-6">1. 综合评估</h2>
|
|
|
+ <div class="space-y-4">
|
|
|
+ <div class="border-b pb-4">
|
|
|
+ <div class="flex items-center justify-between mb-2">
|
|
|
+ <span class="text-gray-600">录用建议</span>
|
|
|
+ <span :class="{
|
|
|
+ 'text-green-500': candidateInfo.hireRecommendation?.includes('推荐'),
|
|
|
+ 'text-red-500': candidateInfo.hireRecommendation?.includes('不推荐'),
|
|
|
+ 'text-yellow-500': !candidateInfo.hireRecommendation?.includes('推荐') && !candidateInfo.hireRecommendation?.includes('不推荐')
|
|
|
+ }">{{ candidateInfo.hireRecommendation || '无建议' }}</span>
|
|
|
</div>
|
|
|
- <p class="text-gray-600 text-sm">{{ candidateInfo.dimensionDetails[key] }}</p>
|
|
|
+ <p class="text-gray-600 text-sm">{{ candidateInfo.hireReason || '无详细说明' }}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="candidateInfo.strengths && candidateInfo.strengths.length > 0" class="border-b pb-4">
|
|
|
+ <h3 class="font-semibold mb-2">优点</h3>
|
|
|
+ <ul class="list-disc pl-5 text-gray-600 text-sm">
|
|
|
+ <li v-for="(strength, index) in candidateInfo.strengths" :key="'strength-'+index">
|
|
|
+ {{ strength }}
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="candidateInfo.weaknesses && candidateInfo.weaknesses.length > 0" class="border-b pb-4">
|
|
|
+ <h3 class="font-semibold mb-2">需改进的地方</h3>
|
|
|
+ <ul class="list-disc pl-5 text-gray-600 text-sm">
|
|
|
+ <li v-for="(weakness, index) in candidateInfo.weaknesses" :key="'weakness-'+index">
|
|
|
+ {{ weakness }}
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div> -->
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- DUV分析评估 -->
|
|
|
+ <!-- 2. DUV分析评估 -->
|
|
|
<div class="mb-8">
|
|
|
- <h2 class="text-xl font-bold mb-6">1. DUV分析评估</h2>
|
|
|
+ <h2 class="text-xl font-bold mb-6">2. DUV分析评估</h2>
|
|
|
<div class="space-y-4">
|
|
|
<div v-for="(item, index) in candidateInfo.duvAnalysis" :key="index" class="border-b pb-4">
|
|
|
<div class="flex items-center justify-between mb-2">
|
|
@@ -736,9 +832,9 @@ const handleVideoError = (event: Event) => {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 面试记录 -->
|
|
|
+ <!-- 3. 面试记录 -->
|
|
|
<div class="mb-8">
|
|
|
- <h2 class="text-xl font-bold mb-6">2. 面试记录</h2>
|
|
|
+ <h2 class="text-xl font-bold mb-6">3. 面试记录</h2>
|
|
|
<div class="space-y-6">
|
|
|
<div v-for="(record, index) in candidateInfo.interviewRecord" :key="index" class="border-b pb-4">
|
|
|
<div class="mb-2">
|
|
@@ -774,9 +870,9 @@ const handleVideoError = (event: Event) => {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 视频记录 -->
|
|
|
+ <!-- 4. 视频记录 -->
|
|
|
<div class="mb-8">
|
|
|
- <h2 class="text-xl font-bold mb-6">3. 视频记录</h2>
|
|
|
+ <h2 class="text-xl font-bold mb-6">4. 视频记录</h2>
|
|
|
<div class="space-y-8">
|
|
|
<div v-for="(category, index) in candidateInfo.videoRecords" :key="index">
|
|
|
<h3 class="text-lg font-semibold mb-4">{{ category.category }}</h3>
|
|
@@ -804,7 +900,7 @@ const handleVideoError = (event: Event) => {
|
|
|
|
|
|
<!-- 其他信息 -->
|
|
|
<div class="mb-8">
|
|
|
- <h2 class="text-xl font-bold mb-6">4. 其他信息</h2>
|
|
|
+ <h2 class="text-xl font-bold mb-6">5. 其他信息</h2>
|
|
|
<div class="space-y-6">
|
|
|
<!-- 验证状态 -->
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
|
@@ -860,93 +956,40 @@ const handleVideoError = (event: Event) => {
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
-
|
|
|
- <!-- 评估表单 -->
|
|
|
- <!-- <a-form
|
|
|
- ref="formRef"
|
|
|
- :model="candidateInfo"
|
|
|
- layout="vertical"
|
|
|
- >
|
|
|
-
|
|
|
- <div class="mb-8">
|
|
|
- <h2 class="text-xl font-bold mb-4">6. 面试评分</h2>
|
|
|
- <a-form-item
|
|
|
- label="请为候选人打分"
|
|
|
- name="score"
|
|
|
- :rules="[{ required: true, message: '请选择评分' }]"
|
|
|
- >
|
|
|
- <a-rate
|
|
|
- v-model:value="evaluationScore"
|
|
|
- :count="5"
|
|
|
- allow-half
|
|
|
- />
|
|
|
- </a-form-item>
|
|
|
- </div>
|
|
|
-
|
|
|
-
|
|
|
- <div class="mb-8">
|
|
|
- <h2 class="text-xl font-bold mb-4">评价意见</h2>
|
|
|
- <a-form-item
|
|
|
- label="请输入评价意见"
|
|
|
- name="comments"
|
|
|
- :rules="[{ required: true, message: '请输入评价意见' }]"
|
|
|
- >
|
|
|
- <a-textarea
|
|
|
- v-model:value="evaluationComments"
|
|
|
- :rows="4"
|
|
|
- placeholder="请输入您的评价意见..."
|
|
|
- />
|
|
|
- </a-form-item>
|
|
|
- </div>
|
|
|
-
|
|
|
-
|
|
|
- <div class="flex justify-end">
|
|
|
- <a-button type="primary" @click="handleSubmit">
|
|
|
- 提交评估
|
|
|
- </a-button>
|
|
|
- </div>
|
|
|
- </a-form> -->
|
|
|
</div>
|
|
|
</a-spin>
|
|
|
|
|
|
- <!-- 悬浮按钮 -->
|
|
|
+ <!-- 悬浮按钮 (Element Plus 版本) -->
|
|
|
<div class="fixed right-8 bottom-24 flex flex-col space-y-4">
|
|
|
- <a-button
|
|
|
+ <el-button
|
|
|
type="primary"
|
|
|
- shape="circle"
|
|
|
- class="flex items-center justify-center"
|
|
|
+ style="margin-left: 12px;"
|
|
|
+ circle
|
|
|
@click="handleShare"
|
|
|
>
|
|
|
- <template #icon>
|
|
|
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
- <path d="M15 8a3 3 0 10-2.977-2.63l-4.94 2.47a3 3 0 100 4.319l4.94 2.47a3 3 0 10.895-1.789l-4.94-2.47a3.027 3.027 0 000-.74l4.94-2.47C13.456 7.68 14.19 8 15 8z" />
|
|
|
- </svg>
|
|
|
- </template>
|
|
|
- </a-button>
|
|
|
- <a-button
|
|
|
+ <el-icon><Share /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
type="primary"
|
|
|
- shape="circle"
|
|
|
- class="flex items-center justify-center"
|
|
|
+ circle
|
|
|
@click="handleDownload"
|
|
|
>
|
|
|
- <template #icon>
|
|
|
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
- <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
|
- </svg>
|
|
|
- </template>
|
|
|
- </a-button>
|
|
|
- <a-button
|
|
|
+ <el-icon><Download /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ circle
|
|
|
+ @click="handlePrint"
|
|
|
+ >
|
|
|
+ <el-icon><Printer /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
type="primary"
|
|
|
- shape="circle"
|
|
|
- class="flex items-center justify-center"
|
|
|
+ circle
|
|
|
@click="scrollToTop"
|
|
|
>
|
|
|
- <template #icon>
|
|
|
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
|
- <path fill-rule="evenodd" d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z" clip-rule="evenodd" />
|
|
|
- </svg>
|
|
|
- </template>
|
|
|
- </a-button>
|
|
|
+ <el-icon><ArrowUp /></el-icon>
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
@@ -978,23 +1021,24 @@ const handleVideoError = (event: Event) => {
|
|
|
border-radius: 50%;
|
|
|
}
|
|
|
|
|
|
-/* 修改以下样式以确保页面可以滚动 */
|
|
|
+/* 修改滚动相关样式 */
|
|
|
html, body {
|
|
|
height: 100%;
|
|
|
- min-height: 100%;
|
|
|
- overflow-y: auto !important;
|
|
|
+ scroll-behavior: smooth;
|
|
|
}
|
|
|
|
|
|
-/* 确保主容器不会限制滚动 */
|
|
|
+/* 确保主容器可以正常滚动 */
|
|
|
.max-w-4xl {
|
|
|
position: relative;
|
|
|
- overflow: visible;
|
|
|
+ overflow-y: visible;
|
|
|
+ min-height: 100%;
|
|
|
}
|
|
|
|
|
|
/* 添加以下样式确保内容可以正常滚动 */
|
|
|
body {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
+ overflow-y: auto !important;
|
|
|
}
|
|
|
|
|
|
#app {
|
|
@@ -1003,6 +1047,14 @@ body {
|
|
|
overflow: visible;
|
|
|
}
|
|
|
|
|
|
+/* 确保滚动容器不会被阻止滚动 */
|
|
|
+.ant-layout,
|
|
|
+.ant-layout-content,
|
|
|
+.el-scrollbar__wrap,
|
|
|
+.el-scrollbar__view {
|
|
|
+ overflow-y: auto !important;
|
|
|
+}
|
|
|
+
|
|
|
@media (max-width: 768px) {
|
|
|
.max-w-4xl {
|
|
|
padding: 1rem;
|
|
@@ -1016,4 +1068,49 @@ body {
|
|
|
right: 1rem;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+/* 打印样式 */
|
|
|
+@media print {
|
|
|
+ /* 打印时隐藏悬浮按钮 */
|
|
|
+ .fixed {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 确保内容完整显示 */
|
|
|
+ .max-w-4xl {
|
|
|
+ max-width: 100% !important;
|
|
|
+ padding: 0 !important;
|
|
|
+ margin: 0 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 调整页面边距 */
|
|
|
+ @page {
|
|
|
+ margin: 1cm;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 确保背景色和图片打印 */
|
|
|
+ * {
|
|
|
+ -webkit-print-color-adjust: exact !important;
|
|
|
+ color-adjust: exact !important;
|
|
|
+ print-color-adjust: exact !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 避免视频容器在打印时出现问题 */
|
|
|
+ .video-container {
|
|
|
+ page-break-inside: avoid;
|
|
|
+ break-inside: avoid;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 确保每个主要部分在新页面开始 */
|
|
|
+ h2.text-xl {
|
|
|
+ page-break-before: always;
|
|
|
+ break-before: always;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 第一个标题不需要分页 */
|
|
|
+ h2.text-xl:first-of-type {
|
|
|
+ page-break-before: avoid;
|
|
|
+ break-before: avoid;
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|