Commit 017c7251 authored by 罗超's avatar 罗超

修改

parent a183c1a7
......@@ -28,18 +28,15 @@
<div class="mb-md">{{ x.week }}</div> -->
<div v-for="(y, index) in x.cities" :key="index">
<div class="mt-md">
<span v-if="(i==0 && index==0)||(i==trip.days.length-1 && index==x.cities.length-1)" class="iconfont icon-qizhi mr-xs"></span>
<span v-if="(i == 0 && index == 0) || (i == trip.days.length - 1 && index == x.cities.length - 1)"
class="iconfont icon-qizhi mr-xs"></span>
<span class="pdf-mid">{{ y }}</span>
</div>
<!-- <div class="info-text pdf-small">{{ y.enName }}</div> -->
</div>
</td>
<td>
<div
class="flex-row bold mb-sm"
v-for="(y, index) in x.attractions"
:key="index"
>
<div class="flex-row bold mb-sm" v-for="(y, index) in x.attractions" :key="index">
<div>{{ index + 1 }}.</div>
<div class="ml-sm flex-1">{{ y.name }}</div>
</div>
......@@ -47,8 +44,8 @@
<td>
<div class="flex-row mb-md" v-if="x.hotels && x.hotels.length > 0">
<span class="iconfont icon-shuijue primary-text"></span>
<div class="flex-1 ml-sm" v-if="i<trip.days.length-1">
<div v-for="(y, index) in x.hotels" :key="index" :class="{'mb-sm': index < x.hotels.length - 1}">
<div class="flex-1 ml-sm" v-if="i < trip.days.length - 1">
<div v-for="(y, index) in x.hotels" :key="index" :class="{ 'mb-sm': index < x.hotels.length - 1 }">
<div class="bold">
<span>{{ y.name }}</span>
<span v-if="index < x.hotels.length - 1"></span>
......@@ -72,9 +69,11 @@
</div>
</td>
<td>
<!-- <div
<div
class="flex-row"
v-if="(i == 0 && trip.flights.length > 0) || (i == items.length - 1 && trip.flights.length > 1)"
v-for="(traffic, tIndex) in x.traffics"
:key="tIndex"
v-if="traffic.type === '航班'"
>
<div class="column align-center py-sm relative">
<div class="accent-bar-h"></div>
......@@ -86,34 +85,36 @@
</div>
<div class="flex-1 ml-sm">
<div class="bold">
{{ trip.flights[i > 0 ? 1 : 0].startCityName }}
{{ traffic.departure }}
</div>
<div class="pdf-small">
Airport Code {{ trip.flights[i > 0 ? 1 : 0].startCityCode }}
{{ traffic.departureAirport }} ({{ traffic.departureAirportCode }})
</div>
<div class="my-md primary-text bold">
<div>
{{ trip.flights[i > 0 ? 1 : 0].items[0].flightNo }}
<span v-if="trip.flights[i > 0 ? 1 : 0].items.length > 1">
{{ trip.flights[i > 0 ? 1 : 0].items.length }}个航班
</span>
{{ traffic.airline }} {{ traffic.flightNumber }}
</div>
<div class="pdf-small alifont">
{{ trip.flights[i > 0 ? 1 : 0].items[0].startTime }} -
{{ trip.flights[i > 0 ? 1 : 0].items[trip.flights[i > 0 ? 1 : 0].items.length - 1].endTime }}
<span v-if="trip.flights[i > 0 ? 1 : 0].items[0].startDate != trip.flights[i > 0 ? 1 : 0].items[trip.flights[i > 0 ? 1 : 0].items.length - 1].endDate">
(+1天)
</span>
{{ traffic.departureTime }} - {{ traffic.arrivalTime }}
</div>
</div>
<div class="bold">
{{ trip.flights[i > 0 ? 1 : 0].endCityName }}
{{ traffic.destination }}
</div>
<div class="pdf-small">
Airport Code {{ trip.flights[i > 0 ? 1 : 0].endCityCode }}
{{ traffic.arrivalAirport }} ({{ traffic.arrivalAirportCode }})
</div>
</div>
</div> -->
</div>
<div
class="flex-row"
v-for="(traffic, tIndex) in x.traffics"
:key="tIndex"
v-if="traffic.type != '航班'"
>
<div class="bold">{{ traffic.type }}</div>
</div>
</td>
</tr>
</tbody>
......@@ -240,7 +241,7 @@
</template>
<script>
import {itineraryService} from '@/services/itinerary';
import { itineraryService } from '@/services/itinerary';
export default {
name: 'ItineraryDetail',
......@@ -295,11 +296,11 @@ export default {
x.cities = [];
const departures = x.traffics.map(y => y.departure)
const destinations = x.traffics.map(y => y.destination)
const attractionsCities = x.attractions.map(y=>y.city)
const cateringsCities = x.caterings.map(y=>y.city)
const hotelsCities = x.hotels.map(y=>y.city)
let temp = [...new Set([...departures,...destinations, ...attractionsCities, ...cateringsCities, ...hotelsCities])]
temp = temp.filter(y => y !== null && y !== undefined && y !== '' && !y.includes('实际')&& !y.includes('周边')&& !y.includes('或'))
const attractionsCities = x.attractions.map(y => y.city)
const cateringsCities = x.caterings.map(y => y.city)
const hotelsCities = x.hotels.map(y => y.city)
let temp = [...new Set([...departures, ...destinations, ...attractionsCities, ...cateringsCities, ...hotelsCities])]
temp = temp.filter(y => y !== null && y !== undefined && y !== '' && !y.includes('实际') && !y.includes('周边') && !y.includes('或'))
temp = temp.map(y => {
y = y.replace('省', '').replace('市', '').replace('区', '').replace('县', '')
return y
......@@ -319,7 +320,7 @@ export default {
const currentDate = new Date(travelDate.getTime() + i * 24 * 60 * 60 * 1000);
temp.num = 'D' + (i + 1);
temp.date = `${(currentDate.getMonth() + 1).toString().padStart(2, '0')}-${currentDate.getDate().toString().padStart(2, '0')}`;
temp.week = ['周日','周一','周二','周三','周四','周五','周六'][currentDate.getDay()];
temp.week = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][currentDate.getDay()];
temp.cities = [];
if (i === 0) {
if (this.trip.flights.length > 0) temp.price += parseFloat(this.trip.flights[0].displayPrice);
......@@ -363,6 +364,7 @@ export default {
this.typePrices.flight += parseFloat(x.displayPrice);
});
},
printPDF() {
window.print();
},
......@@ -372,12 +374,14 @@ export default {
<style scoped>
@import url('//at.alicdn.com/t/c/font_4020685_jbpfa13faji.css');
@font-face {
font-family: 'alifont';
src: url('//at.alicdn.com/wf/webfont/MQHUV6e56ce5/FZ2wHD6g3frR.woff2') format('woff2'),
url('//at.alicdn.com/wf/webfont/MQHUV6e56ce5/wXT52gRya21s.woff') format('woff');
font-display: swap;
}
.full-box {
width: 100vw;
height: 100vh;
......@@ -386,47 +390,208 @@ export default {
box-sizing: border-box;
overflow: auto;
}
.alifont { font-family: 'alifont', 等线 !important; }
.pdf-content { width: 21cm; margin: 0 auto; font-family: 等线 !important; }
.pdf-table { background: #eef0f2; width: 100%; }
.pdf-table th, .pdf-table td { padding-left: 16px; padding-right: 16px; text-align: left; }
.pdf-table td { background: #fff; padding-top: 12px; padding-bottom: 12px; font-size: 12px; vertical-align: top; page-break-inside: avoid; }
.pdf-table th { font-weight: bolder; font-size: 16px; padding-top: 8px; padding-bottom: 8px; }
.pdf-small { font-weight: 400; font-size: 9px; }
.pdf-mid { font-size: 14px; font-weight: 800; }
.title-main { font-size: 30px; color: #409EFF; font-weight: bold; margin-bottom: 12px; }
.city-list { color: #666; margin-bottom: 12px; }
.primary-bar { width: 80px; height: 4px; background: #409EFF; margin-bottom: 16px; }
.flex-row { display: flex; flex-direction: row; }
.align-center { align-items: center; }
.column { display: flex; flex-direction: column; }
.bold { font-weight: bold; }
.primary-text { color: #409EFF; }
.info-text { color: #999; }
.negative-text { color: #e53935; }
.h6 { font-size: 18px; }
.subtitle2 { font-size: 16px; }
.text-right { text-align: right; }
.text-center { text-align: center; }
.flex-1 { flex: 1; }
.mt-md { margin-top: 16px; }
.mb-md { margin-bottom: 16px; }
.mt-lg { margin-top: 32px; }
.mb-lg { margin-bottom: 32px; }
.mt-xl { margin-top: 48px; }
.my-md { margin-top: 16px; margin-bottom: 16px; }
.mb-sm { margin-bottom: 8px; }
.mt-md { margin-top: 16px; }
.ml-sm { margin-left: 8px; }
.mr-xs { margin-right: 4px; }
.pa-md { padding: 16px; }
.pa-sm { padding: 8px; }
.py-sm { padding-top: 8px; padding-bottom: 8px; }
.primary-bar { background: #409EFF; }
.accent-bar-h { height: 2px; width: 14px; background: #409EFF; }
.accent-bar-v { width: 2px; flex: 1; background: #409EFF; }
.icon-feiji-box { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) rotate(180deg); background: #fff; border-radius: 100%; padding: 2px; }
.relative { position: relative; }
.alifont {
font-family: 'alifont', 等线 !important;
}
.pdf-content {
width: 21cm;
margin: 0 auto;
font-family: 等线 !important;
}
.pdf-table {
background: #eef0f2;
width: 100%;
}
.pdf-table th,
.pdf-table td {
padding-left: 16px;
padding-right: 16px;
text-align: left;
}
.pdf-table td {
background: #fff;
padding-top: 12px;
padding-bottom: 12px;
font-size: 12px;
vertical-align: top;
page-break-inside: avoid;
}
.pdf-table th {
font-weight: bolder;
font-size: 16px;
padding-top: 8px;
padding-bottom: 8px;
}
.pdf-small {
font-weight: 400;
font-size: 9px;
}
.pdf-mid {
font-size: 14px;
font-weight: 800;
}
.title-main {
font-size: 30px;
color: #409EFF;
font-weight: bold;
margin-bottom: 12px;
}
.city-list {
color: #666;
margin-bottom: 12px;
}
.primary-bar {
width: 80px;
height: 4px;
background: #409EFF;
margin-bottom: 16px;
}
.flex-row {
display: flex;
flex-direction: row;
}
.align-center {
align-items: center;
}
.column {
display: flex;
flex-direction: column;
}
.bold {
font-weight: bold;
}
.primary-text {
color: #409EFF;
}
.info-text {
color: #999;
}
.negative-text {
color: #e53935;
}
.h6 {
font-size: 18px;
}
.subtitle2 {
font-size: 16px;
}
.text-right {
text-align: right;
}
.text-center {
text-align: center;
}
.flex-1 {
flex: 1;
}
.mt-md {
margin-top: 16px;
}
.mb-md {
margin-bottom: 16px;
}
.mt-lg {
margin-top: 32px;
}
.mb-lg {
margin-bottom: 32px;
}
.mt-xl {
margin-top: 48px;
}
.my-md {
margin-top: 16px;
margin-bottom: 16px;
}
.mb-sm {
margin-bottom: 8px;
}
.mt-md {
margin-top: 16px;
}
.ml-sm {
margin-left: 8px;
}
.mr-xs {
margin-right: 4px;
}
.pa-md {
padding: 16px;
}
.pa-sm {
padding: 8px;
}
.py-sm {
padding-top: 8px;
padding-bottom: 8px;
}
.primary-bar {
background: #409EFF;
}
.accent-bar-h {
height: 2px;
width: 14px;
background: #409EFF;
}
.accent-bar-v {
width: 2px;
flex: 1;
background: #409EFF;
}
.icon-feiji-box {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) rotate(180deg);
background: #fff;
border-radius: 100%;
padding: 2px;
}
.relative {
position: relative;
}
.pdf-float-btn {
position: fixed;
right: 32px;
......@@ -437,12 +602,13 @@ export default {
color: #fff;
padding: 14px 28px;
border-radius: 28px;
box-shadow: 0 2px 8px rgba(64,158,255,0.15);
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
cursor: pointer;
font-size: 16px;
font-weight: bold;
transition: background 0.2s;
}
.pdf-float-btn:hover {
background: #337ecc;
}
......@@ -451,12 +617,16 @@ export default {
@page {
size: A4;
}
@media print {
@page {
size: A4 portrait;
margin: 0;
}
html, body, .pdf-content {
html,
body,
.pdf-content {
width: 100% !important;
min-width: 0 !important;
max-width: 100% !important;
......@@ -464,26 +634,33 @@ export default {
padding: 0 !important;
background: unset !important;
}
.pdf-table {
width: 100% !important;
}
.pdf-float-btn {
display: none !important;
}
.full-box {
overflow: visible !important;
height: auto !important;
max-height: none !important;
}
.pdf-content {
width: 21cm !important;
margin: 0 auto !important;
}
.pdf-table tr {
page-break-inside: avoid !important;
break-inside: avoid !important;
}
.customerServiceButton {
display: none;
}
}
</style>
......@@ -50,25 +50,7 @@
<template slot-scope="scope">
<el-button type="text" size="small" @click="handleEdit(scope.row)">修改</el-button>
<el-button type="text" size="small" style="color:#f56c6c;margin-right: 10px;" @click="handleDelete(scope.row)">删除</el-button>
<el-dropdown trigger="click" @command="(command) => handleCommand(command, scope.row)">
<el-button type="text" size="small">
更多<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="wechatH5" divided>
<i class="el-icon-document"></i> 微信H5行程书
</el-dropdown-item>
<el-dropdown-item command="pdfItinerary">
<i class="el-icon-document"></i> PDF行程书
</el-dropdown-item>
<el-dropdown-item command="webSchedule" divided>
<i class="el-icon-tickets"></i> Web行程单
</el-dropdown-item>
<el-dropdown-item command="pdfSchedule">
<i class="el-icon-tickets"></i> PDF行程单
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button type="text" size="small" @click="openPublishDialog(scope.row)">发布行程</el-button>
</template>
</el-table-column>
</el-table>
......@@ -95,6 +77,45 @@
</el-tabs>
</div>
</div>
<el-dialog
title="发布行程"
:visible.sync="publishDialogVisible"
width="400px"
@close="resetPublishDialog"
append-to-body
>
<div class="publish-options-grid">
<div
v-for="item in publishOptions"
:key="item.command"
class="publish-option-card"
:class="{ active: currentPublishOption === item.command }"
@click="handlePublishOption(item.command)"
>
<!-- <i :class="['el-icon', item.icon]" style="font-size: 28px; margin-bottom: 8px; color: #409EFF;" /> -->
<div class="option-label">{{ item.label }}</div>
</div>
</div>
<div class="qrcode-area" style="margin: 24px 0; text-align: center;" v-loading="createQrcode">
<div style="height: 100px; width: 100px; background: #f5f5f5; display: inline-block; border-radius: 8px; line-height: 100px;">
<!-- 这里放二维码图片 -->
<img :src="qrcodeUrl" style="width: 100%; height: 100%;" />
</div>
<div style="margin-top: 8px; color: #888; font-size: 12px;">扫码查看行程</div>
</div>
<el-input
v-model="publishShareLink"
readonly
placeholder="这里是分享链接"
style="width: 100%;"
v-if="currentPublishOption!='wechatH5'"
>
<template #append>
<el-button @click="copyPublishLink">复制</el-button>
</template>
</el-input>
<div style="height: 30px;"></div>
</el-dialog>
</el-drawer>
</template>
......@@ -102,6 +123,7 @@
import { requirementService } from '@/services/requirement';
import { itineraryService } from '@/services/itinerary';
import InfoCard from './InfoCard.vue';
import QRCode from 'qrcode'
export default {
name: 'RequirementDetail',
......@@ -124,7 +146,19 @@ export default {
activeTab: 'basic',
loading: false,
detail: this.getInitialDetailState(),
baseHref:'http://'+location.host+'/#'
baseHref:'http://'+location.host+'/#',
publishDialogVisible: false,
publishRow: null,
publishShareLink: '',
publishOptions: [
{ command: 'wechatH5', label: '小程序行程书', icon: 'el-icon-document' },
{ command: 'pdfItinerary', label: 'TD行程书', icon: 'el-icon-document' },
{ command: 'webSchedule', label: 'Web行程单', icon: 'el-icon-tickets' },
{ command: 'pdfSchedule', label: 'PDF行程单', icon: 'el-icon-tickets' }
],
currentPublishOption: 'wechatH5',
qrcodeUrl: '',
createQrcode: false
}
},
computed: {
......@@ -207,6 +241,15 @@ export default {
handleEdit(row) {
window.open(`${this.baseHref}/itinerary/edit/${row.id}`, '_blank');
},
async handleTravelPdf(id) {
try {
const response = await itineraryService.getPdfItinerary(id);
return response;
} catch (error) {
this.$message.error('获取行程PDF失败');
return null;
}
},
handleDelete(row) {
this.$confirm(`是否确认删除【${row.title}】?`, '提示', {
type: 'warning'
......@@ -220,26 +263,64 @@ export default {
}
}).catch(() => {});
},
handleCommand(command, row) {
openPublishDialog(row) {
this.publishRow = row;
this.publishDialogVisible = true;
// 默认分享链接,可根据实际需求生成
this.publishShareLink = '';
this.qrcodeUrl = '';
this.createQrcode = false;
this.currentPublishOption = 'wechatH5';
this.handlePublishOption(this.currentPublishOption);
},
resetPublishDialog() {
this.publishDialogVisible = false;
this.publishRow = null;
this.publishShareLink = '';
},
handlePublishOption(command) {
const row = this.publishRow;
switch (command) {
case 'wechatH5':
this.handleWechatH5(row);
this.currentPublishOption = 'wechatH5';
break;
case 'pdfItinerary':
this.handlePDFItinerary(row);
this.currentPublishOption = 'pdfItinerary';
break;
case 'webSchedule':
this.handleWebSchedule(row);
this.currentPublishOption = 'webSchedule';
break;
case 'pdfSchedule':
this.handlePDFSchedule(row);
this.currentPublishOption = 'pdfSchedule';
break;
}
},
copyPublishLink() {
if (navigator.clipboard) {
navigator.clipboard.writeText(this.publishShareLink).then(() => {
this.$message.success('复制成功');
});
} else {
const input = document.createElement('input');
input.value = this.publishShareLink;
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
this.$message.success('复制成功');
}
},
handleWechatH5(row) {
// 生成微信H5链接并打开
const url = `${this.baseHref}/itinerary/h5/${row.id}`;
window.open(url, '_blank');
// const url = `${this.baseHref}/itinerary/h5/${row.id}`;
// window.open(url, '_blank');
this.qrcodeUrl='';
this.createQrcode = true;
this.getMiniProgramQrcode(row.autoId);
},
handlePDFItinerary(row) {
// 下载PDF行程书
......@@ -247,11 +328,61 @@ export default {
},
handleWebSchedule(row) {
// 打开Web行程单页面
window.open(`${this.baseHref}/itinerary/detail/${row.id}`, '_blank');
// window.open(`${this.baseHref}/itinerary/detail/${row.id}`, '_blank');
this.createQrcode = true;
this.canvasQrCode(`${this.baseHref}/itinerary/detail/${row.id}`)
this.publishShareLink = `${this.baseHref}/itinerary/detail/${row.id}`;
},
handlePDFSchedule(row) {
async handlePDFSchedule(row) {
// 下载PDF行程单
window.open(`${this.baseHref}/api/itinerary/pdf/schedule/${row.id}`, '_blank');
//window.open(`${this.baseHref}/api/itinerary/pdf/schedule/${row.id}`, '_blank');
this.createQrcode = true;
let pdfName = await this.handleTravelPdf(row.id);
this.qrcodeUrl = '';
this.publishShareLink = '';
if (pdfName) {
pdfName = pdfName.replace('"', '');
const url = `https://imgfile.oytour.com/aipdf/${pdfName}`;
console.log(url);
this.canvasQrCode(url)
this.publishShareLink = url;
}
},
getMiniProgramQrcode(id) {
let urlObj = this.domainManager();
this.$http({
headers: {
'Content-Type': 'application/json'
},
method: 'post',
url: urlObj.DomainUrl + '/api/file/GetAiTravelInfo',
data: {
"msg": {
"id": id
}
}
}).then(res => {
if (res.data.resultCode === 1) {
this.qrcodeUrl = "data:image/png;base64,"+res.data.data
this.createQrcode = false;
}
}).catch(err => {
this.createQrcode = false;
this.$message.error('获取小程序二维码失败');
})
},
canvasQrCode(text) {
let that = this
QRCode.toDataURL(text, {
color: {
dark: "000",
light: "#fff"
}
}, function (err, url) {
that.qrcodeUrl = url
that.createQrcode = false;
})
},
getTimelineItemType(state) {
const typeMap = {
......@@ -316,4 +447,37 @@ export default {
color: #606266;
font-size: 13px;
}
.publish-options-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px 24px;
justify-items: center;
margin-bottom: 24px;
margin-top: 8px;
}
.publish-option-card {
width: 140px;
height: 60px;
background: #f7fafd;
border-radius: 12px;
box-shadow: 0 1px 4px rgba(64,158,255,0.06);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: box-shadow 0.2s, background 0.2s;
border: 1px solid #e6f0fa;
}
.publish-option-card:hover,
.publish-option-card.active {
background: #e6f7ff;
box-shadow: 0 4px 16px rgba(64,158,255,0.12);
}
.option-label {
font-size: 14px;
color: #333;
font-weight: 500;
}
</style>
......@@ -122,7 +122,7 @@ export default {
let locationName = window.location.hostname;
let isOnline = 0; //0-本地测试,1-线上
let ocrUrl = "http://192.168.5.46:8888";
domainUrl = "http://192.168.5.46";
domainUrl = "http://reborn.oytour.com";
let crmLocalFileStreamDownLoadUrl = "";
crmLocalFileStreamDownLoadUrl = locationName.indexOf('oytour') !== -1 ? "http://crm.oytour.com" : "http://testcrm.oytour.com";
let javaUrldo = "";
......
......@@ -4,7 +4,7 @@ import axios from 'axios';
// 创建 axios 实例
const service = axios.create({
baseURL: process.env.NODE_ENV === 'production'?'http://rw.oytour.com/api/app':'http://rw.oytour.com/api/app',//'http://localhost:19001/api/app', // 通用前缀
timeout: 10000
timeout: 1000 * 60
});
// 请求拦截器:自动加 token
......
......@@ -28,6 +28,15 @@ export const itineraryService = {
return rwRequest.delete('/travel/'+id);
},
/**
* 获取行程PDF
* @param {string} id - 行程ID
* @returns {Promise} - API 响应
*/
getPdfItinerary: (id) => {
return rwRequest.get('/travel/'+id+"/pdf");
},
/**
* 更新行程
* @param {Object} data - 行程数据
......
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