Commit 6c4eadd6 authored by 罗超's avatar 罗超

新的出团通知书

parent d907e038
......@@ -29,9 +29,10 @@
"file-saver": "^2.0.0",
"fs": "^0.0.1-security",
"google-translate-api": "^2.3.0",
"html-to-image": "^1.11.13",
"html2canvas": "^1.0.0-alpha.12",
"js-md5": "^0.7.3",
"jspdf": "^2.5.1",
"jspdf": "^3.0.3",
"lrz": "^4.9.40",
"mapbox-gl": "^2.15.0",
"moment": "^2.24.0",
......
......@@ -256,7 +256,7 @@
body {
margin: 0;
padding: 0;
background: #f9f9f9 url(assets/img/img-bg.png) no-repeat bottom left/100% auto;
background: #f9f9f9 no-repeat bottom left/100% auto;/*url(assets/img/img-bg.png) */
height: 100%;
}
......
......@@ -5448,6 +5448,16 @@
});
},
toTrip: function (obj, TicketUnionId, GuestIds) {
this.$router.push({
name: 'TravelNoticeDownLoad',
params: {
id: obj.TCID,
orderId: obj.OrderId,
guestIds: GuestIds?GuestIds:'0',
unionfid: TicketUnionId?TicketUnionId:0
}
})
return
this.tripObj.tcid = obj.TCID;
this.tripObj.configId = obj.ConfigId;
this.tripObj.orderId = obj.OrderId;
......@@ -6930,10 +6940,22 @@
},
//出团通知书
getOrderInfo() {
if(this.ConfigData.Config.OutNotice == 1) this.SetOutNotice()
else{
this.getFlightInfo();
this.getCombinTeam();
}
},
SetOutNotice() {
this.$router.push({
name: 'TravelNoticePreview',
params: {
id:this.ConfigData.Config.TCID,
tab: '出团通知书预览',
target:'y'
}
})
return
var that = this;
this.Confirm(
this.$t('objFill.shifou') + "【" +
......
......@@ -463,6 +463,9 @@
<el-button type="primary" icon="el-icon-edit"
@click="isShowDIv = true,divTitle='修改线路',updateData(index)"></el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="配置线路出团通知书模版" placement="top-start">
<el-button icon="el-icon-s-flag" @click="handlerNoticeTemplate(item.lineID)"></el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="$t('system.btn_CtrlSet')" placement="top-start">
<el-button type="success" icon="el-icon-setting" @click="getBranchList(item.lineID)"></el-button>
</el-tooltip>
......@@ -847,6 +850,9 @@
};
},
methods: {
handlerNoticeTemplate(lineID) {
this.$router.push({ path: '/TravelNoticeConfig', query: { lineId: lineID, blank: 'y', tab: '出团通知书配置' } });
},
handleChange3(file, fileList) {
this.fileList3 = fileList.slice(-1);
},
......
......@@ -118,6 +118,7 @@
</div>
</div>
<div class="operate-btn">
<el-button v-if="state" size="small" @click="handleBack">返回</el-button>
<el-button type="primary" size="small" @click="saveContent">保存</el-button>
</div>
<!-- 自定义预览弹窗 -->
......@@ -272,7 +273,8 @@ export default {
lineId:0,
tcid:0,
orderId:0,
loading:true
loading:true,
state:0
}
},
mounted() {
......@@ -293,6 +295,7 @@ export default {
});
if(this.$route.query.lineId) this.lineId = this.$route.query.lineId;
else if(this.$route.query.tcid) this.tcid = this.$route.query.tcid;
if(this.$route.query.state) this.state = this.$route.query.state;
if(this.lineId) this.loadLineNoticeInfo();
else if(this.tcid) this.loadTravelNoticeInfo();
},
......@@ -307,6 +310,19 @@ export default {
this.handleClosePreview();
}
},
// 展开根节点
expandRootNode() {
this.$nextTick(() => {
if (this.$refs.tree && this.configList.length > 0) {
this.$refs.tree.setCurrentKey(this.configList[0].id);
// 展开根节点
const rootNodeId = this.configList[0].id;
if (this.$refs.tree.store.nodesMap[rootNodeId]) {
this.$refs.tree.store.nodesMap[rootNodeId].expanded = true;
}
}
});
},
loadLineNoticeInfo() {
if(!this.lineId) return
this.apipost("travel_post_GetNoticeConfigList", {
......@@ -315,6 +331,7 @@ export default {
if(res.data.resultCode == 1 && res.data.data.length > 0) {
this.configList = JSON.parse(res.data.data[0].Content);
this.loading=false
this.expandRootNode()
}else{
this.loadLine()
}
......@@ -328,6 +345,7 @@ export default {
if(res.data.resultCode == 1 && res.data.data.Content !='') {
this.configList = JSON.parse(res.data.data.Content);
this.loading=false
this.expandRootNode()
}else{
this.loadTravel()
......@@ -360,6 +378,7 @@ export default {
this.configList = JSON.parse(res.data.data[0].Content);
this.resolverTravel(t)
this.loading=false
this.expandRootNode()
}else{
this.$confirm('当前线路没有配置出团通知书模版,无法生存出团通知书,是否立即前往配置', '提示', {
confirmButtonText: '确定',
......@@ -664,19 +683,13 @@ export default {
},
// 取消编辑
cancelEdit() {
this.$confirm('确定要取消编辑吗?未保存的内容将丢失。', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 清除选中状态
this.selectedNode = null;
this.$refs.tree.setCurrentKey(null);
this.$message.info('已取消编辑');
}).catch(() => {
// 取消操作
});
handleBack() {
this.$router.push({
name: 'TravelNoticePreview',
params: {
id:this.state
}
})
},
// 富文本编辑器内容变化
......
<template>
<div>
<div class="cover-page">
<div style="height: auto; min-height: 100%;" v-loading="loading">
<div v-if="modeName == 'preview'" class="preview-notice-tools">
<el-button size="mini" @click="loadTravel(1)">刷新</el-button>
<el-button type="warning" size="mini" v-if="OutNotice == 0" @click="handleOutNotice(1)">确认通知书内容</el-button>
<el-button type="danger" size="mini" v-if="OutNotice == 1" @click="handleOutNotice(0)">取消出团通知书</el-button>
<el-button type="primary" size="mini" @click="handleEdit">编辑</el-button>
</div>
<div v-else-if="modeName == 'download' && OutNotice!=0" class="preview-notice-tools">
<el-button type="primary" size="mini" @click="handleDownload" :loading="downloadLoading">
{{ downloadLoading ? '正在生成PDF...' : '下载出团通知书' }}
</el-button>
</div>
<template v-if="!loading && !(modeName == 'download' && OutNotice==0)">
<div class="cover-page" id="coverPage">
<div class="notice">出团通知书</div>
<div class="qr-code">
<img :src="qrCode" alt="qr-code" v-if="qrCode && qrCode !== ''">
<img src="https://im.oytour.com/tripImg/638957874541961011.png" v-else/>
<img :src="`data:image/png;base64,${qrCode}`" alt="qr-code" v-if="qrCode && qrCode !== ''">
<img src="https://im.oytour.com/tripImg/638957874541961011.png" v-else />
<div class="qr-code-text">有意见,码上写</div>
</div>
<div class="desc">
......@@ -18,14 +30,15 @@
</div>
</div>
<!-- 隐藏的测量容器 -->
<div class="PreviewNotice measure-preview" style="position: absolute; visibility: hidden; width: 1280px; pointer-events: none;" ref="measureContainer">
<div class="PreviewNotice measure-preview"
style="position: absolute; visibility: hidden; width: 1280px; pointer-events: none;" ref="measureContainer">
<div ref="baseInfo" class="base-info">
<div class="base-info-item left">
<div class="iconbox">
<iconpark-icon name="local" size="34" fill="#dcaa8b" stroke="#193877"></iconpark-icon>
</div>
<div class="info-content">
<div class="info-title">驻日大使馆联系方式</div>
<div class="info-title">大使馆联系方式</div>
<div class="info-content">
<div v-for="(item, index) in noticeInfo.contacts.embassy" :key="index">{{ item }}</div>
</div>
......@@ -74,7 +87,8 @@
</tr>
<tr>
<td colspan="4">
<div style="line-height: 1.2;" v-for="(item, index) in noticeInfo.flightNote" :key="index">{{ item }}</div>
<div style="line-height: 1.2;" v-for="(item, index) in noticeInfo.flightNote" :key="index">{{ item
}}</div>
</td>
</tr>
</table>
......@@ -99,14 +113,17 @@
<div v-for="(section, sIndex) in noticeInfo.sections" :key="sIndex" :ref="`section-${sIndex}`">
<div :ref="`section-title-${sIndex}`" class="body-box">
<div style="padding-bottom: 20px;">
<div class="big-title" v-if="section.title">{{ section.title }}</div>
</div>
<div v-if="section.subtitle" class="sub-title">{{ section.subtitle }}</div>
</div>
<div v-for="(item, iIndex) in section.items" :key="iIndex" :ref="`section-${sIndex}-item-${iIndex}`" class="body-box">
<div v-for="(item, iIndex) in section.items" :key="iIndex" :ref="`section-${sIndex}-item-${iIndex}`"
class="body-box">
<!-- 文本内容 -->
<div v-if="item.type === 'text'" class="body-content">
<template v-if="item.value!='tripPlan'">{{ item.value }}</template>
<template v-if="item.value=='tripPlan'">
<template v-if="item.value != 'tripPlan'">{{ item.value }}</template>
<template v-if="item.value == 'tripPlan'">
<div :ref="`trip-plan-header`">
<table class="trip-list">
<tr>
......@@ -123,7 +140,7 @@
<tr>
<td style="width: 100px;">
<div class="date-td">{{ tripItem.date }}</div>
<div class="date-no">D{{ index+1 }}</div>
<div class="date-no">D{{ index + 1 }}</div>
</td>
<td>
<div style="line-height: 1.2;">{{ tripItem.trip }}</div>
......@@ -146,7 +163,7 @@
<td style="width: 400px;">
<div>{{ tripItem.hotel }}</div>
<div>地址:{{ tripItem.hotelAddress }}</div>
<div v-if="tripItem.hotelPhone!=''">电话:{{ tripItem.hotelPhone }}</div>
<div v-if="tripItem.hotelPhone != ''">电话:{{ tripItem.hotelPhone }}</div>
</td>
</tr>
</table>
......@@ -175,7 +192,8 @@
<div class="start-time">出发时间:{{ noticeInfo.startTime }}</div>
</div>
<div class="move-one">
<iconpark-icon name="move-one" size="20" fill="#dcaa8b" stroke="#dcaa8b" style="transform: rotate(90deg);"></iconpark-icon>
<iconpark-icon name="move-one" size="20" fill="#dcaa8b" stroke="#dcaa8b"
style="transform: rotate(90deg);"></iconpark-icon>
<span style="margin-left: 10px;">集合地点:{{ noticeInfo.meetingPlace }}</span>
</div>
</div>
......@@ -195,7 +213,9 @@
</div>
<div class="info-content">
<div class="info-title">驻日大使馆联系方式</div>
<div class="info-content"><div v-for="(item, index) in noticeInfo.contacts.embassy" :key="index">{{ item }}</div></div>
<div class="info-content">
<div v-for="(item, index) in noticeInfo.contacts.embassy" :key="index">{{ item }}</div>
</div>
</div>
</div>
<div class="base-info-item">
......@@ -240,7 +260,10 @@
<td>{{ flight.time }}</td>
</tr>
<tr>
<td colspan="4"><div style="line-height: 1.2;" v-for="(item, index) in noticeInfo.flightNote" :key="index">{{ item }}</div></td>
<td colspan="4">
<div style="line-height: 1.2;" v-for="(item, index) in noticeInfo.flightNote" :key="index">
{{ item }}</div>
</td>
</tr>
</table>
</div>
......@@ -249,27 +272,32 @@
</div>
<!-- 分隔线 -->
<div v-else-if="block.type === 'split-line'" :key="`${pageIndex}-${blockIndex}-split`" class="split-line-big"></div>
<div v-else-if="block.type === 'split-line'" :key="`${pageIndex}-${blockIndex}-split`"
class="split-line-big"></div>
<!-- 证件提示 -->
<div v-else-if="block.type === 'document'" :key="`${pageIndex}-${blockIndex}-doc`" style="text-align: center;">
<div v-else-if="block.type === 'document'" :key="`${pageIndex}-${blockIndex}-doc`"
style="text-align: center;">
<div class="card-box">{{ noticeInfo.document.title }}</div>
<div class="card-import" v-html="noticeInfo.document.content"></div>
</div>
<!-- Section标题 -->
<div v-else-if="block.type === 'section-title'" :key="`${pageIndex}-${blockIndex}-title`" class="body-box">
<div v-else-if="block.type === 'section-title'" :key="`${pageIndex}-${blockIndex}-title`"
class="body-box">
<div style="padding-bottom: 20px;">
<div class="big-title" v-if="block.content.title">{{ block.content.title }}</div>
</div>
<div v-if="block.content.subtitle" class="sub-title">{{ block.content.subtitle }}</div>
</div>
<!-- Section条目 -->
<div v-else-if="block.type === 'section-item'" :key="`${pageIndex}-${blockIndex}-item`" class="body-box">
<div class="body-content">
<template v-if="block.content!='tripPlan'">
<template v-if="block.content != 'tripPlan'">
{{ block.content }}
</template>
<template v-if="block.content=='tripPlan'">
<template v-if="block.content == 'tripPlan'">
<table class="trip-list">
<tr>
<td style="width: 100px;">日期</td>
......@@ -283,7 +311,7 @@
<tr>
<td style="width: 100px;">
<div class="date-td">{{ item.date }}</div>
<div class="date-no">D{{ index+1 }}</div>
<div class="date-no">D{{ index + 1 }}</div>
</td>
<td>
<div style="line-height: 1.2;">{{ item.trip }}</div>
......@@ -306,7 +334,7 @@
<td style="width: 400px;">
<div>{{ item.hotel }}</div>
<div>地址:{{ item.hotelAddress }}</div>
<div v-if="item.hotelPhone!=''">电话:{{ item.hotelPhone }}</div>
<div v-if="item.hotelPhone != ''">电话:{{ item.hotelPhone }}</div>
</td>
</tr>
</table>
......@@ -315,7 +343,8 @@
</div>
<!-- 行程计划表格头部 -->
<div v-else-if="block.type === 'trip-plan-header'" :key="`${pageIndex}-${blockIndex}-trip-header`" class="body-box">
<div v-else-if="block.type === 'trip-plan-header'" :key="`${pageIndex}-${blockIndex}-trip-header`"
class="body-box">
<div class="body-content trip-plan-header">
<table class="trip-list">
<tr>
......@@ -330,7 +359,8 @@
</div>
<!-- 行程计划表格行 -->
<div v-else-if="block.type === 'trip-plan-row'" :key="`${pageIndex}-${blockIndex}-trip-row`" class="body-box">
<div v-else-if="block.type === 'trip-plan-row'" :key="`${pageIndex}-${blockIndex}-trip-row`"
class="body-box">
<div class="body-content trip-plan-row" :class="{ 'trip-plan-row-last': block.isLastRow }">
<table class="trip-list">
<tr>
......@@ -359,7 +389,7 @@
<td style="width: 400px;">
<div>{{ block.content.hotel }}</div>
<div>地址:{{ block.content.hotelAddress }}</div>
<div v-if="block.content.hotelPhone!=''">电话:{{ block.content.hotelPhone }}</div>
<div v-if="block.content.hotelPhone != ''">电话:{{ block.content.hotelPhone }}</div>
</td>
</tr>
</table>
......@@ -367,7 +397,8 @@
</div>
<!-- 图片块 -->
<div v-else-if="block.type === 'section-image'" :key="`${pageIndex}-${blockIndex}-image`" class="body-box">
<div v-else-if="block.type === 'section-image'" :key="`${pageIndex}-${blockIndex}-image`"
class="body-box">
<div class="image-container">
<img :src="block.content" class="section-image" alt="内容图片" />
</div>
......@@ -378,16 +409,25 @@
</div>
</div>
</div>
</template>
<div style="height: 100%;display: flex; justify-content: center;align-items: center;" v-else-if="modeName == 'download' && OutNotice==0">
<div style="text-align: center;">
<div style="font-size: 24px;color: #333;">OP还未开启出团通知书</div>
<div style="font-size: 16px;color: #666;">请在OP开启后再进行出团通知书下载,如果确认OP已开启,仍然无法下载,请通知管理员</div>
</div>
</div>
</div>
</template>
<script>
import * as htmlToImage from 'html-to-image';
import jsPDF from 'jspdf'
export default {
name: 'PreviewNotice',
props: {
noticeData: {
type: Object,
default: () => {}
default: () => { }
},
TCID: {
type: String,
......@@ -400,8 +440,9 @@ export default {
pageHeight: 1703,
firstPageHeaderHeight: 439,
otherPageHeaderHeight: 109,
footerHeight: 57,
footerHeight: 0,
loading: true,
downloadLoading: false, // 下载PDF的loading状态
// 原始数据
noticeInfo: {
groupNo: '',
......@@ -441,7 +482,7 @@ export default {
hotel: '关空美景花园酒店',
hotelAddress: '〒598-0006 大阪府泉佐野市市場西3-3-34',
hotelPhone: '0724691112',
flights:[
flights: [
{
date: '09-06',
no: 'MU2025',
......@@ -525,17 +566,34 @@ export default {
qrCode: '',
// 计算后的分页数据
pages: [],
id:0,
imageLoadTimer: null // 图片加载防抖定时器
id: 0,
sourceData: {},
imageLoadTimer: null, // 图片加载防抖定时器
modeName:'template',
OutNotice:0,
orderId:0,
guestIds:'',
unionfid:0,
msg:{}
}
},
created() {
console.log(this.$route)
if (this.$route.params.id) this.id = this.$route.params.id
if(this.$route.params.orderId) this.orderId = this.$route.params.orderId
if(this.$route.params.guestIds) this.guestIds = this.$route.params.guestIds
if(this.$route.params.unionfid) this.unionfid = this.$route.params.unionfid
if(this.$route.name=='TravelNoticePreview'){
this.modeName = 'preview'
this.msg = {TCID:this.id}
}
else if(this.$route.name=='TravelNoticeDownLoad'){
this.modeName = 'download'
this.msg = {TCID:this.id,OrderId:this.orderId,GuestIds:this.guestIds,Unionfid:this.unionfid}
}
},
mounted() {
this.resolverNoticeData()
// 等待字体加载完成后再计算分页
this.waitForFontsAndCalculate()
if(this.TCID && this.TCID!='') this.id = this.TCID
if(this.$route.query.tcid) this.id = this.$route.query.tcid
this.loadTravel()
this.initPage()
},
watch: {
noticeInfo: {
......@@ -547,19 +605,365 @@ export default {
},
noticeData: {
handler() {
//this.noticeInfo = this.noticeData
this.resolverNoticeData()
this.sourceData = this.noticeData
if(this.modeName == 'template') {
this.resolverNoticeData(1)
}
},
deep: true
}
},
methods: {
resolverNoticeData() {
//this.noticeInfo = this.noticeData
async handleDownload(){
try {
this.downloadLoading = true;
// 等待字体加载
await this.loadFonts();
// 收集所有需要截图的元素
const elements = this.collectElements();
console.log('收集到的元素:', elements);
if (elements.length === 0) {
this.$message.error('没有找到需要导出的内容');
this.downloadLoading = false;
return;
}
console.log(`准备截图 ${elements.length} 个页面`);
// 截图所有元素
const images = await this.captureElements(elements);
if (images.length === 0) {
this.$message.error('截图失败');
this.downloadLoading = false;
return;
}
console.log(`成功截图 ${images.length} 个页面,开始生成PDF`);
// 生成 PDF
await this.generatePDF(images);
this.$message.success('PDF 导出成功');
this.downloadLoading = false;
} catch (error) {
console.error('导出失败:', error);
this.$message.error('导出失败: ' + error.message);
this.downloadLoading = false;
}
},
// 加载字体
async loadFonts() {
try {
await Promise.all([
document.fonts.load('1em SourceHanSerifCN-Regular'),
document.fonts.load('1em SourceHanSerifCN-Bold'),
document.fonts.load('400 1em SourceHanSerifCN-Regular'),
document.fonts.load('700 1em SourceHanSerifCN-Bold'),
document.fonts.load('bold 1em SourceHanSerifCN-Bold')
]);
console.log('字体加载完成');
await new Promise(resolve => setTimeout(resolve, 500));
} catch (error) {
console.warn('字体加载失败:', error);
await new Promise(resolve => setTimeout(resolve, 500));
}
},
// 收集所有需要截图的元素
collectElements() {
const elements = [];
// 1. 封面页
const coverPage = document.getElementById('coverPage');
if (coverPage) {
elements.push({
element: coverPage,
name: '封面页'
});
}
// 2. 所有 PreviewNotice 页面
const previewPages = document.querySelectorAll('.PreviewNotice:not(.measure-preview)');
previewPages.forEach((page, index) => {
elements.push({
element: page,
name: `第${index + 1}页`
});
});
return elements;
},
// 将伪元素转换为真实 DOM 元素
convertPseudoElements(element) {
const converted = [];
// 处理所有带有伪元素的节点
const processNode = (node) => {
if (node.nodeType !== 1) return; // 只处理元素节点
const beforeStyle = window.getComputedStyle(node, '::before');
const afterStyle = window.getComputedStyle(node, '::after');
// 处理 ::before
if (beforeStyle && beforeStyle.content && beforeStyle.content !== 'none' && beforeStyle.content !== '""') {
const beforeDiv = document.createElement('div');
beforeDiv.className = '__pseudo-before__';
// 复制样式
this.copyPseudoStyles(beforeStyle, beforeDiv.style);
// 插入到节点开头
node.insertBefore(beforeDiv, node.firstChild);
converted.push({ node, element: beforeDiv, type: 'before' });
}
// 处理 ::after
if (afterStyle && afterStyle.content && afterStyle.content !== 'none' && afterStyle.content !== '""') {
const afterDiv = document.createElement('div');
afterDiv.className = '__pseudo-after__';
// 复制样式
this.copyPseudoStyles(afterStyle, afterDiv.style);
// 插入到节点末尾
node.appendChild(afterDiv);
converted.push({ node, element: afterDiv, type: 'after' });
}
// 递归处理子节点
Array.from(node.children).forEach(child => {
if (!child.className?.includes('__pseudo-')) {
processNode(child);
}
});
};
processNode(element);
return converted;
},
// 复制伪元素样式到真实元素
copyPseudoStyles(computedStyle, targetStyle) {
const importantStyles = [
'width', 'height', 'background', 'backgroundImage', 'backgroundSize',
'backgroundPosition', 'backgroundRepeat', 'position', 'top', 'right',
'bottom', 'left', 'display', 'content', 'transform', 'margin', 'padding'
];
importantStyles.forEach(prop => {
const value = computedStyle.getPropertyValue(prop);
if (value && value !== 'none' && value !== 'auto') {
targetStyle.setProperty(prop, value);
}
});
// 处理 content 属性
if (computedStyle.content && computedStyle.content !== 'none') {
targetStyle.content = computedStyle.content;
}
},
// 移除转换的伪元素
removePseudoElements(converted) {
converted.forEach(({ element }) => {
if (element && element.parentNode) {
element.parentNode.removeChild(element);
}
});
},
// 截图所有元素
async captureElements(elements) {
const images = [];
for (let i = 0; i < elements.length; i++) {
const { element, name } = elements[i];
console.log(`正在截图: ${name} (${i + 1}/${elements.length})`);
// 转换伪元素
const convertedPseudos = this.convertPseudoElements(element);
try {
// 临时移除 margin
const originalMargin = element.style.margin;
element.style.margin = '0';
// 强制浏览器重新计算样式
element.style.display = 'none';
element.offsetHeight;
element.style.display = '';
// 等待一下,确保样式生效
await new Promise(resolve => setTimeout(resolve, 50));
// 截图
const dataUrl = await htmlToImage.toJpeg(element, {
quality: 0.95,
pixelRatio: 1,
cacheBust: true,
skipFonts: false, // 保留字体
// 过滤掉外部资源,避免 CORS 错误和性能问题
filter: (node) => {
// 排除外部样式表链接
if (node.tagName === 'LINK' && node.rel === 'stylesheet') {
const href = node.href || '';
// 排除所有外部域名的样式表(特别是 mapbox)
if (href.includes('mapbox') ||
(href.startsWith('http') && !href.includes(window.location.hostname))) {
return false;
}
}
// 排除可能引起 CORS 问题的外部资源
if (node.tagName === 'SCRIPT' || node.tagName === 'IFRAME') {
return false;
}
return true;
},
// 忽略 CSS 处理错误,继续生成图片
onclone: (clonedDoc) => {
// 移除可能导致问题的外部样式表
const externalLinks = clonedDoc.querySelectorAll('link[rel="stylesheet"]');
externalLinks.forEach(link => {
const href = link.href || '';
if (href.includes('mapbox') ||
(href.startsWith('http') && !href.includes(window.location.hostname))) {
link.remove();
}
});
}
});
// 恢复 margin
element.style.margin = originalMargin;
// 获取元素尺寸
const rect = element.getBoundingClientRect();
images.push({
dataUrl,
width: rect.width,
height: rect.height,
name
});
console.log(`${name} 截图完成 (${rect.width}x${rect.height})`);
// 短暂延迟,避免浏览器卡顿
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
console.error(`${name} 截图失败:`, error);
throw new Error(`${name} 截图失败`);
} finally {
// 移除转换的伪元素
this.removePseudoElements(convertedPseudos);
}
}
return images;
},
// 生成 PDF
async generatePDF(images) {
if (images.length === 0) return;
// 使用第一张图片的尺寸创建 PDF(转换为 mm)
const firstImage = images[0];
const pdfWidth = firstImage.width * 0.264583; // px to mm
const pdfHeight = firstImage.height * 0.264583;
// 创建 PDF,orientation 根据宽高比自动选择
const orientation = pdfWidth > pdfHeight ? 'landscape' : 'portrait';
const pdf = new jsPDF({
orientation,
unit: 'mm',
format: [pdfWidth, pdfHeight],
compress: true
});
console.log(`PDF 尺寸: ${pdfWidth.toFixed(2)}mm x ${pdfHeight.toFixed(2)}mm`);
// 添加所有图片到 PDF
for (let i = 0; i < images.length; i++) {
const image = images[i];
if (i > 0) {
// 如果当前图片尺寸不同,需要添加新的页面尺寸
const imgWidth = image.width * 0.264583;
const imgHeight = image.height * 0.264583;
if (Math.abs(imgWidth - pdfWidth) > 1 || Math.abs(imgHeight - pdfHeight) > 1) {
// 尺寸不同,添加自定义尺寸的页面
pdf.addPage([imgWidth, imgHeight], imgWidth > imgHeight ? 'landscape' : 'portrait');
pdf.addImage(image.dataUrl, 'JPEG', 0, 0, imgWidth, imgHeight);
} else {
// 尺寸相同,使用默认尺寸
pdf.addPage();
pdf.addImage(image.dataUrl, 'JPEG', 0, 0, pdfWidth, pdfHeight);
}
} else {
// 第一页
pdf.addImage(image.dataUrl, 'JPEG', 0, 0, pdfWidth, pdfHeight);
}
console.log(`已添加 ${image.name} 到PDF (${i + 1}/${images.length})`);
}
// 生成文件名
const fileName = `出团通知书_${this.noticeInfo.groupNo || ''}_${new Date().getTime()}.pdf`;
// 保存 PDF
pdf.save(fileName);
console.log(`PDF 已导出: ${fileName}`);
},
async initPage(){
await this.waitForFontsAndCalculate()
if (this.noticeData && this.modeName == 'template') {
this.sourceData = this.noticeData
this.resolverNoticeData(2)
}
// 等待字体加载完成后再计算分页
if (this.TCID && this.TCID != '') this.id = this.TCID
// if(this.$route.query.tcid) this.id = this.$route.query.tcid
if (this.id) {
this.loadTravelNoticeInfo()
this.loadTravelNoticeStatus()
} else {
this.loading = false
}
},
loadTravelNoticeInfo() {
if (!this.id) return
this.apipost("travel_post_GetTravelNotice", {
TCID: this.id
}, res => {
if (res.data.resultCode == 1 && res.data.data.Content != '') {
this.sourceData = JSON.parse(res.data.data.Content)[0];
this.resolverNoticeData(3)
this.loadQrCode()
this.loading = false
this.loadTravel(3)
} else {
this.loadTravel(2)
}
});
},
resolverNoticeData(s) {
//#region 基础信息
const baseInfo = this.noticeData.children.find(item => item.key === 'baseInfo').children
const baseInfo = this.sourceData.children.find(item => item.key === 'baseInfo').children
this.noticeInfo.groupNo = baseInfo.find(item => item.key === 'travelNo').showContent[0].value
console.log('baseInfo',this.sourceData,s)
this.noticeInfo.tripName = baseInfo.find(item => item.key === 'travelName').showContent[0].value
this.noticeInfo.startTime = baseInfo.find(item => item.key === 'travelTime').showContent[0].value
this.noticeInfo.meetingPlace = baseInfo.find(item => item.key === 'travelPlace').showContent[0].value
......@@ -582,23 +986,23 @@ export default {
this.noticeInfo.flightNote = baseInfo.find(item => item.key === 'travelFlightRemind').showContent.map(item => item.value)
//#endregion
//#region 证件提示
const importInfo = this.noticeData.children.find(item => item.key === 'requiredDocuments')
const importInfo = this.sourceData.children.find(item => item.key === 'requiredDocuments')
this.noticeInfo.document.title = importInfo.label
this.noticeInfo.document.content = importInfo.showContent.map(x=>x.value).join('<br/>')
this.noticeInfo.document.content = importInfo.showContent.map(x => x.value).join('<br/>')
//#endregion
const others = this.noticeData.children.filter(item => !item.key)
others.forEach(x=>{
const others = this.sourceData.children.filter(item => !item.key)
this.noticeInfo.sections = []
others.forEach(x => {
let items = {
title:x.label
title: x.label
}
if(x.children && x.children.length > 0){
if (x.children && x.children.length > 0) {
items.subtitle = x.children[0].label
// 处理包含文本和图片的混合内容
items.items = this.processShowContent(x.children[0].showContent)
this.noticeInfo.sections.push(items)
x.children.forEach((y,i)=>{
if(i>0){
x.children.forEach((y, i) => {
if (i > 0) {
let childItems = {
subtitle: y.label,
items: this.processShowContent(y.showContent)
......@@ -606,7 +1010,7 @@ export default {
this.noticeInfo.sections.push(childItems)
}
})
}else{
} else {
items.items = this.processShowContent(x.showContent)
this.noticeInfo.sections.push(items)
}
......@@ -624,15 +1028,76 @@ export default {
}
})
},
loadTravel(){
if(!this.id) return
this.apipost("b2b_get_GetTravelPriceNotice", {
TCID: this.id
}, res => {
if(res.data.resultCode == 1) {
loadTravel(reload) {
if (!this.id) return
this.apipost("b2b_get_GetTravelPriceNotice", this.msg, res => {
if (res.data.resultCode == 1) {
const t = res.data.data
const tripPlan = t.DayList.map(x=>{
const flights = t.FlightList.filter(y=>y.DateStr==x.DateStr).map(y=>{
if(reload==1) {
this.resolverTravel(t,true)
}else if(reload==2){
this.apipost("travel_post_GetNoticeConfigList", {
LineId: t.LineId
}, res => {
if (res.data.resultCode == 1 && res.data.data.length > 0) {
this.sourceData = JSON.parse(res.data.data[0].Content)[0];
this.resolverNoticeData(4)
this.resolverTravel(t,true)
this.loading = false
} else {
this.$confirm('当前线路没有配置出团通知书模版,无法生存出团通知书,是否立即前往配置', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
showCancelButton:false,
showClose:false,
closeOnClickModal:false,
closeOnPressEscape:false,
closeOnHashChange:false
}).then(() => {
this.$router.push({
name: 'TravelNoticeConfig',
query: {
lineId: t.LineId,
state:this.id,
}
})
})
}
});
}else{
this.resolverTravel(t,false)
}
}
});
},
resolverTravel(t,reload) {
if(reload || this.unionfid) {
const flights = t.FlightList.map(item => {
return {type: 'text', value: `${item.DayStr},${item.DepartureName}-${item.ArrivalName},${item.FlightNumber},${item.DepartureTime}-${item.ArrivalTime},${item.AlName}`}
})
const baseInfo = [
{ id: '1-1-1', key:'travelNo', label: '团号',showContent:[{type: 'text', value: t.TCNUM}], editable: false, editing: false },
{ id: '1-1-2', key:'travelName', label: '团名',showContent:[{type: 'text', value: `${t.LtName}·${t.DayNum}天`}], editable: false, editing: false },
{ id: '1-1-3', key:'travelTime', label: '出发时间',showContent:[{type: 'text', value: `${t.StartDate}(${t.Week}) ${t.GatheringTimeInfo}`}], editable: false, editing: false },
{ id: '1-1-4', key:'travelPlace', label: '集合地点',showContent:[{type: 'text', value: `${t.GatheringAddress}`}], editable: false, editing: false },
{ id: '1-1-6', key:'travelLeader', label: '全程领队', showContent:[{type: 'text', value: `${t.LeaderName} ${t.LeaderTel}`}], editable: false, editing: false },
{ id: '1-1-7', key:'travelGuide', label: '当地导游', showContent:[{type: 'text', value: `${t.GuideName} ${t.GuideTel}`}], editable: false, editing: false },
{ id: '1-1-8', key:'travelEmergency', label: '紧急联系人', showContent:[{type: 'text', value: t.EmergencyContactInfo}], editable: false, editing: false },
{ id: '1-1-9', key:'travelFlight', label: '航班信息', showContent:flights, editable: false, editing: false },
]
for(let i=0;i<this.sourceData.children[0].children.length;i++){
const id = this.sourceData.children[0].children[i].id
const baseInfoItem = baseInfo.find(item => item.id === id)
if(baseInfoItem){
this.sourceData.children[0].children[i].showContent = baseInfoItem.showContent
}
}
if(reload) this.saveTravelNoticeInfo()
this.resolverNoticeData(5)
}
const tripPlan = t.DayList.map(x => {
const flights = t.FlightList.filter(y => y.DateStr == x.DateStr).map(y => {
return {
date: y.DayStr,
no: y.FlightNumber,
......@@ -647,15 +1112,36 @@ export default {
trip: x.Title,
car: '单接机',
meal: `早:${x.BreakFastName}\n中:${x.LunchName}\n晚:${x.DinnerName}`,
hotel: x.HotelList.map(y=>y.HotelName).join('/'),
hotelAddress: x.HotelList&&x.HotelList.length==1?x.HotelList[0].HotelAddress:'',
hotelPhone: x.HotelList&&x.HotelList.length==1?x.HotelList[0].HotelPhone:'',
hotel: x.HotelList.map(y => y.HotelName).join('/'),
hotelAddress: x.HotelList && x.HotelList.length == 1 ? x.HotelList[0].HotelAddress : '',
hotelPhone: x.HotelList && x.HotelList.length == 1 ? x.HotelList[0].HotelPhone : '',
flights
}
})
this.tripPlan = tripPlan
this.loadQrCode()
},
loadQrCode() {
let urlObj = this.domainManager();
this.$http({
headers: {
'Content-Type': 'application/json'
},
method: 'post',
url: urlObj.DomainUrl + '/api/file/GetGuestSurvey',
data: {
"msg": {
TCID: this.id
}
});
}
}).then(res => {
if (res.data.resultCode === 1) {
this.qrCode = res.data.data;
}
}).catch(err => {
})
},
// 等待字体加载完成
async waitForFontsAndCalculate() {
......@@ -733,14 +1219,15 @@ export default {
setTimeout(() => {
const contentBlocks = []
// 辅助函数:获取元素的完整高度(包括margin和line-height的影响)
// 辅助函数:获取元素的完整高度(包括margin、padding和line-height的影响)
const getFullHeight = (el) => {
const rect = el.getBoundingClientRect()
const style = window.getComputedStyle(el)
const marginTop = parseFloat(style.marginTop) || 0
const marginBottom = parseFloat(style.marginBottom) || 0
const paddingTop = parseFloat(style.paddingTop) || 0
const paddingBottom = parseFloat(style.paddingBottom) || 0
// 注意:rect.height 已经包含了 padding,所以不需要再次添加
// 但是需要考虑 margin 的影响
// 获取 line-height,这可能导致实际高度大于 rect.height
const lineHeight = style.lineHeight
......@@ -756,14 +1243,9 @@ export default {
actualLineHeight = parseFloat(lineHeight) * fontSize
}
// 如果 line-height 大于 fontSize,可能需要额外的空间
// 特别是对于 .body-content 元素,line-height 是 42px
const lineHeightExtra = Math.max(0, actualLineHeight - fontSize)
// rect.height 已经包含了 padding 和部分 line-height 效果
// 但在某些情况下,实际渲染可能需要更多空间
// 添加一个小的缓冲区(5%)来处理浏览器渲染误差
const heightBuffer = rect.height * 0.05
// rect.height 已经包含了 padding 和内容高度
// 只需要添加 margin 和一个小的缓冲区来处理浏览器渲染误差
const heightBuffer = Math.max(5, rect.height * 0.02) // 最小5px,最大2%的缓冲
const totalHeight = Math.ceil(rect.height + marginTop + marginBottom + heightBuffer)
......@@ -903,13 +1385,13 @@ export default {
blocks: []
}
// 第一页的可用高度(增加更大的安全边距)
// 第一页的可用高度(适度的安全边距)
// 安全边距需要考虑:
// 1. CSS line-height (42px) 可能导致的额外高度
// 2. 浏览器渲染误差和亚像素渲染
// 3. margin 折叠等 CSS 特性
// 4. 表格、边框等元素的额外空间
const safetyMargin = 50 // 增加到200px安全边距,确保内容不会溢出
// 3. 表格、边框等元素的额外空间
// 由于现在使用 padding 而不是 margin,高度计算更准确,可以减少安全边距
const safetyMargin = 30 // 适度的安全边距,避免过度保守导致分页过早
let availableHeight = this.pageHeight - this.firstPageHeaderHeight - this.footerHeight - safetyMargin
let currentHeight = 0
......@@ -933,8 +1415,8 @@ export default {
}
// 判断是否需要换页
// 添加额外的安全阈值(50px),提前换页以避免溢出
const pageBreakThreshold = 50
// 添加适度的安全阈值,提前换页以避免溢出
const pageBreakThreshold = 20 // 减少阈值,让分页更精确
if (currentHeight + block.height + pageBreakThreshold > availableHeight) {
// 保存当前页
this.pages.push(currentPage)
......@@ -968,6 +1450,58 @@ export default {
}
}, 100)
},
handleEdit(){
this.$router.push({
name: 'TravelNoticeConfig',
query: {
tcid: this.id,
state:this.id
}
})
},
loadTravelNoticeStatus(){
this.apipost(
"travel_get_GetTravelConfigTCID", {
TCID: this.id,
},
(res) => {
if (res.data.resultCode == 1) {
this.OutNotice = res.data.data.config.OutNotice;
}
}
);
},
handleOutNotice(type){
if(this.loading) return
this.loading = true
this.apipost(
"travel_post_SetOutNotice", {
TCID: this.id,
OutNotice: type,
}, (res) => {
if (res.data.resultCode == 1) {
this.saveTravelNoticeInfo()
this.loadTravelNoticeStatus()
} else {
this.Error(res.data.message);
}
this.loading = false
}
)
},
saveTravelNoticeInfo(){
const notices = [this.sourceData]
this.apipost("travel_post_SetTravelNotice", {
TCID: this.id,
Content: JSON.stringify(notices)
}, res => {
if(res.data.resultCode == 1){
}else{
this.$message.error(res.data.message)
}
})
}
}
}
......@@ -978,10 +1512,25 @@ export default {
font-family: 'SourceHanSerifCN-Regular';
src: url('https://im.oytour.com/tripfont/SourceHanSerifCN-Regular.otf') format(opentype);
}
@font-face {
font-family: 'SourceHanSerifCN-Bold';
src: url('https://im.oytour.com/tripfont/SourceHanSerifCN-Bold.otf') format(opentype);
}
.preview-notice-tools{
height: 54px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 20px;
background: #fff;
position: sticky;
top: 0;
left: 0;
margin-bottom: 20px;
z-index: 5;
}
.PreviewNotice,
.cover-page {
width: 1280px;
......@@ -993,96 +1542,117 @@ export default {
/* padding-bottom: 57px; */
overflow: hidden;
box-sizing: border-box;
margin: 0 auto;
}
.PreviewNotice * {
line-height: 1;
}
.cover-page{
.cover-page {
background: url('https://im.oytour.com/tripImg/638957870917977223.jpg') no-repeat 0% 0%;
padding:120px 74px 64px 74px;
padding: 120px 74px 64px 74px;
}
.cover-page .notice{
.cover-page .notice {
font-weight: 800;
font-size: 104px;
color: #101519;
font-family: 'SourceHanSerifCN-Bold';
}
.cover-page .qr-code{
.cover-page .qr-code {
margin-top: 44px;
}
.cover-page .qr-code img{
.cover-page .qr-code img {
width: 188px;
height: 188px;
}
.cover-page .qr-code .qr-code-text{
.cover-page .qr-code .qr-code-text {
font-weight: 600;
font-size: 22px;
color: #03153A;
margin-top: 10px;
}
.cover-page .desc{
.cover-page .desc {
margin-top: 700px;
}
.cover-page .logobox{
.cover-page .logobox {
text-align: right;
margin-top:100px;
margin-top: 100px;
}
.cover-page .logobox img{
.cover-page .logobox img {
width: auto;
height: 65px;
}
.cover-page .desc div{
.cover-page .desc div {
font-weight: 500;
font-size: 32px;
color: #101519;
line-height: 48px;
}
.PreviewNotice .trip-list{
.PreviewNotice .trip-list {
width: 100%;
font-size: 18px;
border-collapse: collapse;
background: #F4F0E6;
border: 1px solid #E7E0CE;
}
/* .PreviewNotice .trip-list:last-child{
margin-bottom: 30px;
} */
/* 表格行之间不需要额外的 margin,因为每个行都是独立的 table */
.PreviewNotice .body-box .body-content > div[class*='trip-plan'] {
margin-bottom: 0;
.PreviewNotice .body-box .body-content>div[class*='trip-plan'] {
padding-bottom: 0;
}
/* 移除表格相关 body-content 的 margin-bottom,使表格看起来连贯 */
.PreviewNotice .body-content.trip-plan-header,
.PreviewNotice .body-content.trip-plan-row {
margin-bottom: 0;
padding-bottom: 0;
}
/* 只有最后一个表格行才需要 margin-bottom,与原有表格样式保持一致 */
.PreviewNotice .body-content.trip-plan-row-last {
margin-bottom: 30px;
padding-bottom: 30px;
}
/* 确保表格头部后面的第一行表格上边框不重叠 */
.PreviewNotice .trip-list + .trip-list {
.PreviewNotice .trip-list+.trip-list {
border-top: none;
}
/* 当表格行在页面顶部时(换页后),保留顶部边框 */
.PreviewNotice .body-box:first-of-type .trip-list {
border-top: 1px solid #E7E0CE;
}
.PreviewNotice .trip-list .date-td{
color:#0F2350;
.PreviewNotice .trip-list .date-td {
color: #0F2350;
font-family: 'SourceHanSerifCN-Bold';
}
.PreviewNotice .trip-list .date-td .date-no{
.PreviewNotice .trip-list .date-td .date-no {
font-size: 18px;
margin-top: 5px;
}
.PreviewNotice .trip-list .flightInfo{
.PreviewNotice .trip-list .flightInfo {
font-size: 16px;
margin-top: 20px;
color:#0f2350a7;
color: #0f2350a7;
}
.PreviewNotice .trip-list td{
.PreviewNotice .trip-list td {
border-right: 1px solid #DCD2B9;
border-bottom: 1px solid #DCD2B9;
padding: 8px;
......@@ -1091,13 +1661,16 @@ export default {
color: #000000;
font-family: 'SourceHanSerifCN-Regular';
}
.PreviewNotice .trip-list td:last-child{
.PreviewNotice .trip-list td:last-child {
border-right: none;
}
.PreviewNotice .title{
.PreviewNotice .title {
font-family: 'SourceHanSerifCN-Bold';
}
.PreviewNotice .tc-no{
.PreviewNotice .tc-no {
font-weight: 600;
font-size: 22px;
color: #FFFFFF;
......@@ -1107,14 +1680,16 @@ export default {
text-align: center;
}
.PreviewNotice .tc-name{
.PreviewNotice .tc-name {
font-weight: 600;
font-size: 39px;
color: #FFFFFF;
margin-top: 15px;
text-align: center;
}
.PreviewNotice .main-title{
.PreviewNotice .main-title {
/* font-weight: 600; */
font-size: 66px;
color: #FFFFFF;
......@@ -1122,19 +1697,22 @@ export default {
text-align: center;
font-family: 'SourceHanSerifCN-Bold';
}
.PreviewNotice .split-line{
.PreviewNotice .split-line {
width: 100px;
height: 7px;
background: #dcaa8b;
border-radius: 4px;
margin: 24px auto;
}
.PreviewNotice .split-line-big{
.PreviewNotice .split-line-big {
height: 1px;
background: #D6CAAD;
margin: 40px 45px;
}
.PreviewNotice .start-time{
.PreviewNotice .start-time {
width: auto;
height: 50px;
background: rgba(255, 255, 255, 0.1);
......@@ -1146,7 +1724,8 @@ export default {
color: #FFFFFF;
display: inline-block;
}
.PreviewNotice-header .move-one{
.PreviewNotice-header .move-one {
display: flex;
justify-content: center;
align-items: center;
......@@ -1155,7 +1734,8 @@ export default {
font-size: 22px;
color: #FFFFFF;
}
.PreviewNotice-header.small{
.PreviewNotice-header.small {
height: 109px;
background: linear-gradient(90deg, #193877, #102554);
position: relative;
......@@ -1166,7 +1746,8 @@ export default {
/* font-family: 'SourceHanSerifCN-Bold'; */
margin-bottom: 39px;
}
.PreviewNotice-header.small::before{
.PreviewNotice-header.small::before {
background: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Upload/Goods/638956940819119044.png') no-repeat center center;
background-size: 100% 100%;
position: absolute;
......@@ -1177,19 +1758,23 @@ export default {
display: inline-block;
content: ' ';
}
.PreviewNotice .base-info{
.PreviewNotice .base-info {
padding: 47px 0 0 112px;
display: flex;
flex-wrap: wrap;
gap: 60px;
}
.PreviewNotice .base-info .base-info-item.left{
.PreviewNotice .base-info .base-info-item.left {
width: 610px;
}
.PreviewNotice .base-info .base-info-item{
.PreviewNotice .base-info .base-info-item {
display: flex;
}
.PreviewNotice .base-info .base-info-item .iconbox{
.PreviewNotice .base-info .base-info-item .iconbox {
width: 54px;
height: 54px;
background: #E7E0CE;
......@@ -1198,33 +1783,40 @@ export default {
text-align: center;
margin-right: 20px;
}
.PreviewNotice .base-info .base-info-item .info-content{
.PreviewNotice .base-info .base-info-item .info-content {
flex: 1;
}
.PreviewNotice .base-info .base-info-item .info-content .info-title{
.PreviewNotice .base-info .base-info-item .info-content .info-title {
font-size: 28px;
color: #0F2350;
font-family: 'SourceHanSerifCN-Bold';
}
.PreviewNotice .base-info .base-info-item .info-content .info-content{
.PreviewNotice .base-info .base-info-item .info-content .info-content {
font-weight: 400;
font-size: 26px;
color: #000000;
font-family: 'SourceHanSerifCN-Regular';
margin-top: 14px;
}
.PreviewNotice .base-info .base-info-item table{
.PreviewNotice .base-info .base-info-item table {
border: none;
border-collapse: collapse;
width: 100%;
}
.PreviewNotice .base-info .base-info-item table td{
.PreviewNotice .base-info .base-info-item table td {
padding-bottom: 14px;
}
.PreviewNotice .base-info .base-info-item table tr:last-child td{
.PreviewNotice .base-info .base-info-item table tr:last-child td {
padding-bottom: 0;
padding-top: 8px;
}
.PreviewNotice-header {
width: 100%;
height: 439px;
......@@ -1233,7 +1825,8 @@ export default {
background-repeat: no-repeat;
background-position: center center;
}
.PreviewNotice .card-box{
.PreviewNotice .card-box {
width: auto;
display: inline-block;
background: #FF002B;
......@@ -1244,15 +1837,17 @@ export default {
color: #FFFFFF;
padding: 7px 14px;
}
.PreviewNotice .card-import{
.PreviewNotice .card-import {
font-weight: 600;
font-size: 28px;
color: #000000;
line-height: 40px;
margin: 24px 45px 90px 45px;
padding: 24px 45px 45px 45px;
text-align: center;
}
.PreviewNotice .big-title{
.PreviewNotice .big-title {
padding: 9px 21px;
background: #12295C;
border-radius: 11px;
......@@ -1261,28 +1856,32 @@ export default {
color: #FFFFFF;
font-family: 'SourceHanSerifCN-Bold';
display: inline-block;
margin-bottom: 20px;
/* margin-bottom: 20px; */
}
.PreviewNotice .sub-title{
.PreviewNotice .sub-title {
font-weight: 800;
font-size: 28px;
color: #0F2350;
font-family: 'SourceHanSerifCN-Bold';
margin: 0px 0 32px 0;
padding: 32px 0px 20px 0px;
}
.PreviewNotice .body-content{
.PreviewNotice .body-content {
font-weight: 400;
font-size: 26px;
color: #000000;
line-height: 42px;
margin-bottom: 20px;
padding-bottom:10px;
font-family: 'SourceHanSerifCN-Regular';
}
/* 图片容器样式 */
.PreviewNotice .image-container {
margin: 20px 0;
text-align: center;
}
.PreviewNotice .section-image {
max-width: 100%;
height: auto;
......@@ -1290,13 +1889,16 @@ export default {
margin: 0 auto;
border-radius: 8px;
}
.PreviewNotice .body-content.content-item{
.PreviewNotice .body-content.content-item {
margin-bottom: 20px;
}
.PreviewNotice .body-box{
.PreviewNotice .body-box {
padding: 0 45px;
}
.PreviewNotice-footer {
height: 22px;
......@@ -1306,6 +1908,7 @@ export default {
left: 0;
right: 0;
}
.PreviewNotice-footer::after {
content: ' ';
display: block;
......
......@@ -6864,5 +6864,21 @@ export default {
title: '编辑报价单'
}
},
{
path: '/TravelNoticePreview/:id',
name: 'TravelNoticePreview',
component: resolve => require(['@/pages/travel-notice/PreviewNotice'], resolve),
meta: {
title: '出团通知书预览'
}
},
{
path: '/TravelNoticeDownLoad/:id/:orderId/:guestIds/:unionfid',
name: 'TravelNoticeDownLoad',
component: resolve => require(['@/pages/travel-notice/PreviewNotice'], resolve),
meta: {
title: '出团通知书下载'
}
}
]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment