4202d307 by 杨炀

Merge commit '547289a7' into order

2 parents a586c361 547289a7
......@@ -30,6 +30,7 @@
"jszip": "^3.10.1",
"katex": "^0.16.6",
"lodash": "^4.17.21",
"md5js": "^1.0.7",
"nprogress": "0.2.0",
"pinia": "2.0.35",
"qrcode": "^1.5.3",
......
This diff could not be displayed because it is too large.
......@@ -13,6 +13,7 @@ NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/register', '/regulations']
router.beforeEach((to, from, next) => {
console.log('to2', to)
NProgress.start()
if (getToken()) {
// debugger
......
......@@ -9,6 +9,7 @@ import useUserStore from '@/store/modules/user'
NProgress.configure({ showSpinner: false })
router.beforeEach((to, from, next) => {
console.log('to', to)
NProgress.start()
if (getToken()) {
// 判断当前用户是否已拉取完user_info信息
......
......@@ -500,6 +500,59 @@ export const constantRoutes = [
meta: { title: 'System messages' }
}
]
},
{
path: 'seat',
component: () => import('@/viewsPc/seat/seat'),
name: 'seat',
redirect: '/seat/detail',
children: [
{
path: 'detail',
name: 'seat_detail',
component: () => import('@/viewsPc/seat/ticket-detail'),
meta: { title: '购票详情' },
props: route => ({
activityId:route.query.id,
})
},
{
path: 'seat_picker',
name: 'seat_picker',
component: () => import('@/viewsPc/seat/seat-picker'),
meta: { title: '选座' }
},
{
path: 'order',
name: 'seat_order',
component: () => import('@/viewsPc/seat/order-list'),
meta: { title: '我的订单' }
},
{
path: 'order_detail',
name: 'order_detail',
component: () => import('@/viewsPc/seat/order-detail'),
meta: { title: '订单详情' }
},
{
path: 'confirm_order',
name: 'confirm_order',
component: () => import('@/viewsPc/seat/confirm-order'),
meta: { title: '确认订单' }
},
{
path: 'add_watch_people',
name: 'add_watch_people',
component: () => import('@/viewsPc/seat/add-watch-people'),
meta: { title: '新增观影人' }
},
{
path: 'people_manage',
name: 'people_manage',
component: () => import('@/viewsPc/seat/people-manage'),
meta: { title: '观影人管理' }
},
]
}
]
},
......
......@@ -196,7 +196,7 @@ function submit() {
return
}
if (!timeVal.value.id)return proxy.$modal.msgError('请选择预约时间!', )
if (!timeVal.value.id)return proxy.$modal.msgError( language.value==0?'请选择预约时间!':"Please select an appointment time!" )
proxy.$refs['formRef'].validate(valid=>{
if (valid){
......
<script setup>
import { ElMessage } from "element-plus";
import { addViewPeople } from "./api/index.js";
const router = useRouter();
const people = reactive({
form: {
name: "",
idCard: "",
},
type: "身份证",
onConfirm() {
if (!people.form.name)
return ElMessage({ type: "warning", message: "请输入姓名" });
if (!people.form.idCard)
return ElMessage({ type: "warning", message: "请输入证件号" });
// 使用正则验证身份证号码格式
const idCardRegex =
/^[1-9]\d{5}(19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[\dXx]$/;
if (!idCardRegex.test(people.form.idCard))
return ElMessage({ type: "warning", message: "身份证号格式不正确" });
addViewPeople(people.form).then((res) => {
ElMessage({ type: "success", message: "操作成功" });
router.go(-2);
});
},
});
</script>
<template>
<div class="container">
<div class="title">新增观影人</div>
<div class="content">
<div class="form-item">
<div>
<div class="label">姓名</div>
<el-input
v-model="people.form.name"
style="width: 570px"
placeholder="请输入姓名"
/>
</div>
<div>
<div class="label">证件类型</div>
<el-input
v-model="people.type"
style="width: 570px"
placeholder="Please input"
readonly
/>
</div>
</div>
<div class="form-item">
<div>
<div class="label">身份证号</div>
<el-input
v-model="people.form.idCard"
style="width: 570px"
placeholder="请输入身份证号"
/>
</div>
</div>
</div>
<div class="footer">
<div class="can_pay">取消</div>
<div class="pay" @click="people.onConfirm()">确认</div>
</div>
</div>
</template>
<style scoped lang="scss">
div {
box-sizing: border-box;
}
.container {
padding: 20px 0;
width: 1200px;
margin: 0 auto;
.title {
padding: 11px;
text-align: center;
background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
font-size: 18px;
color: #ffffff;
}
.content {
padding: 46px 20px 24px;
background-color: #fff;
:deep(.el-input) {
height: 48px;
border-radius: 24px !important;
}
.form-item {
display: flex;
gap: 20px;
margin-bottom: 36px;
&:last-child {
margin: 0;
}
.label {
font-size: 18px;
color: #333333;
margin-bottom: 16px;
}
}
}
.footer {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
height: 70px;
background-color: #fff;
margin-top: 12px;
.pay {
width: 200px;
height: 40px;
background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
border-radius: 20px;
font-weight: 500;
font-size: 16px;
color: #ffffff;
line-height: 40px;
text-align: center;
cursor: pointer;
}
.can_pay {
width: 200px;
height: 40px;
background: #f6f6f6;
border-radius: 20px;
font-weight: 500;
font-size: 16px;
color: #999;
line-height: 40px;
text-align: center;
box-sizing: border-box;
cursor: pointer;
}
}
}
</style>
import request from "../utils/request";
export const loginFree = (data) => request("POST", "/login/loginFree", data);
/** 活动详情 */
export const activityDetail = (data) =>
request("GET", `/api/activity/detail/${data.actId}`, data);
/** 场次详情 */
export const sessionDetail = (data) =>
request("GET", `/api/activity/sessionDetail/${data.actId}`, data);
/** 获取场馆信息 */
export const getSitePlaceInfo = (data) =>
request("GET", `/api/activity/getSitePlaceInfo`, data);
/** 获取票档信息 */
export const getPriceLevelInfo = (data) =>
request("GET", `/api/activity/getPriceLevelInfo`, data);
/** 获取座位信息 */
export const getSiteConfig = (data) =>
request("GET", `/api/activity/getSiteConfig`, data);
/** 确认订单 */
export const confirmOrder = (data) =>
request("POST", `/api/order/confirmOrder`, data);
/** 订单支付 */
export const payOrder = (data) =>
request("POST", `/api/order/payment`, data);
/** 观众列表 */
export const viewPeopleList = (data) =>
request("GET", `/api/customer/list`, data);
/** 删除观众 */
export const deleteViewPeople = (data) =>
request("POST", `/api/customer/delete/${data.id}`, data);
/** 新增观众 */
export const addViewPeople = (data) =>
request("POST", `/api/customer/add`, data);
/** 订单列表 */
export const getOrderList = (data) =>
request("GET", `/api/order/list`, data);
/** 立即支付 */
export const immediatePay = (data) =>
request("POST", `/api/order/immediatePay`, data);
/** 取消支付 */
export const cancelPay = (data) =>
request("POST", `/api/order/cancelPay/${data.orderSn}`, data);
/** 退单 */
export const cancelOrder = (data) =>
request("POST", `/api/order/cancelOrder/${data.orderSn}`, data);
/** 订单详情 */
export const getOrderDetail = (data) =>
request("GET", `/api/order/detail/${data.orderSn}`, data);
<script setup>
const props = defineProps({
showCodeDialog: {
type: Boolean,
default: false,
},
qrCode: {
type: String,
default: ''
}
});
</script>
<template>
<div>
<el-dialog v-model="props.showCodeDialog" title="支付" width="300">
<div>
<img class="qrcode" :src="props.qrCode" />
</div>
</el-dialog>
</div>
</template>
<style scoped lang="scss">
.qrcode {
width: 150px;
height: 150px;
background-color: #8623fc;
margin: 0 auto;
}
</style>
<script setup>
import { confirmOrder } from "./api/index.js";
import { ElMessage } from "element-plus";
import { payOrder, viewPeopleList } from "./api/index.js";
import { reactive } from "vue";
import qrCodeDialog from "./components/qrCodeDialog.vue";
const route = useRoute();
const router = useRouter();
const payment = reactive({
showCodeDialog: false,
btn_loading: false,
form: {
viewers: [],
phone: "",
},
qrInfo: {},
paymentHandle() {
if (payment.form.viewers.length != order.data?.seatInfo?.length)
return ElMessage({ type: "warning", message: "观看人与购买票数不符" });
if (!payment.form.phone)
return ElMessage({ type: "warning", message: "请输入联系人电话" });
payOrder({
contactPhone: payment.form.phone,
customerIds: payment.form.viewers,
orderToken: order.data?.orderToken,
payType: 1,
paymentAmount: order.data?.paymentAmount,
}).then((res) => {
// TODO: 这里有一个二维码
payment.qrInfo = res.data;
payment.showCodeDialog = true;
router.push({
path: "/seat/order",
});
});
},
});
const order = reactive({
data: null,
fetchData() {
confirmOrder({
actId: route.query.actId ?? 1,
openType: route.query.openType,
sessionId: route.query.sessionId,
sitePlace: route.query.sitePlace,
ticketType: route.query.ticketType,
seatIds: route.query.seatIds.split(","),
}).then((res) => {
this.data = res.data;
});
},
});
const audience = reactive({
data: [],
fetchData() {
viewPeopleList().then((res) => {
audience.data = res.data;
});
},
});
audience.fetchData();
order.fetchData();
</script>
<template>
<div class="container">
<div class="title">订单确认</div>
<div class="content">
<div class="left">
<div class="info">
<div class="name">{{ order.data?.activityName }}</div>
<div class="address">{{ order.data?.placeName }}</div>
</div>
<div class="ticket_info">
<div class="tit_box">
<div class="line"></div>
<div class="txt">订票信息</div>
</div>
<div class="form">
<el-form>
<el-form-item label="联系人">
<el-input
v-model="payment.form.phone"
placeholder="请输入联系人电话"
style="width: 260px"
/>
</el-form-item>
<el-form-item label="观看人">
<div class="p_box">
<div class="people">
<el-checkbox-group
v-model="payment.form.viewers"
:max="order.data?.seatInfo?.length"
>
<div
v-for="(it, index) in audience.data"
:key="index"
class="prople_item"
>
<div>
<div class="name">{{ it.name }}</div>
<div class="idcard">{{ it.idCard }}</div>
</div>
<el-checkbox :value="it.id"> </el-checkbox>
</div>
</el-checkbox-group>
</div>
<!-- button -->
<div
class="btn"
@click="$router.push({ path: '/seat/people_manage' })"
>
新增
</div>
</div>
</el-form-item>
</el-form>
</div>
</div>
</div>
<div class="right">
<div class="tit_box">
<div class="line"></div>
<div class="txt">订单明细</div>
</div>
<div class="detail">
<div class="detail_top">
<div class="time">{{ order.data?.dateStr }}</div>
<div class="ticket">
{{ order.data?.singlePrice }}元票档 x{{
order.data?.seatInfo?.length
}}
</div>
</div>
<div class="detail_center">
<div
v-for="(it, index) in order.data?.seatInfo"
:key="index"
class="ticket"
>
<span v-if="it.venueId == 1">{{ it.area }}</span>
{{ it.pai }}{{ it.no }}座 ({{
it.venueId == 1 ? "B6" : "B4"
}}馆)
</div>
</div>
<div class="detail_b">
<div class="sum_txt">共计</div>
<div class="price_num">¥{{ order.data?.paymentAmount }}</div>
</div>
</div>
</div>
</div>
<div class="footer">
<div>
<span class="label">共计金额:</span><span class="value">¥900.00</span>
</div>
<div class="pay" @click="payment.paymentHandle()">立即支付</div>
</div>
<qrCodeDialog
:showCodeDialog="payment.showCodeDialog"
:qrCode="payment.qrInfo?.scanCodeUrl"
/>
</div>
</template>
<style scoped lang="scss">
div {
box-sizing: border-box;
}
.qrcode {
width: 150px;
height: 150px;
background-color: #8623fc;
margin: 0 auto;
}
.container {
padding: 20px 0;
width: 1200px;
margin: 0 auto;
.title {
padding: 11px;
text-align: center;
background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
font-size: 18px;
color: #ffffff;
}
.content {
display: flex;
background-color: #fff;
padding: 20px 0;
}
.line {
width: 4px;
height: 18px;
background: linear-gradient(180deg, #493ceb 0%, #8623fc 100%);
border-radius: 4px;
}
.left {
padding-left: 20px;
.info {
width: 640px;
background: rgba(69, 61, 234, 0.04);
border-radius: 8px;
border: 1px solid #d3d1f6;
padding: 20px 0 28px 33px;
margin-bottom: 20px;
.name {
font-weight: 500;
font-size: 18px;
color: #000000;
margin-bottom: 20px;
}
.address {
font-weight: 400;
font-size: 14px;
color: #929aa0;
}
}
.ticket_info {
.tit_box {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 14px;
.txt {
font-weight: bold;
font-size: 16px;
color: #493ceb;
}
}
.form {
width: 640px;
min-height: 464px;
padding: 20px 60px;
border-radius: 5px;
border: 1px solid #dcdfe6;
.p_box {
display: flex;
gap: 10px;
.people {
width: 298px;
background: #fbfcfd;
border-radius: 2px;
border: 1px solid #dcdfe6;
padding: 0 14px;
.prople_item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 0;
border-bottom: 1px solid #dcdfe6;
&:last-child {
border: none;
}
.name {
font-size: 16px;
color: #929aa0;
margin-bottom: 20px;
}
.idcard {
font-size: 10px;
color: #929aa0;
}
}
}
.btn {
width: 90px;
height: 40px;
background: #fbfcfd;
border-radius: 20px;
border: 1px solid #493ceb;
margin-top: 10px;
font-size: 14px;
color: #493ceb;
line-height: 40px;
text-align: center;
cursor: pointer;
user-select: none;
}
}
}
}
}
.right {
width: 460px;
margin-left: 36px;
.tit_box {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
.txt {
font-weight: bold;
font-size: 16px;
color: #493ceb;
}
}
.detail {
padding: 15px 26px;
border-radius: 5px;
border: 1px solid #dcdfe6;
.detail_top {
padding-bottom: 13px;
border-bottom: 1px solid #dcdfe6;
.time {
font-weight: 500;
font-size: 18px;
color: #2d373f;
line-height: 25px;
margin-bottom: 8px;
}
.ticket {
font-size: 16px;
color: #2d373f;
}
}
.detail_center {
margin-top: 14px;
display: flex;
flex-direction: column;
padding-bottom: 13px;
border-bottom: 1px solid #dcdfe6;
gap: 8px;
.ticket {
font-size: 16px;
color: #2d373f;
}
}
.detail_b {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16px;
.sum_txt {
font-weight: 600;
font-size: 18px;
color: #2d373f;
line-height: 25px;
}
.price_num {
font-weight: 600;
font-size: 36px;
color: #ff8124;
line-height: 50px;
}
}
}
}
.footer {
display: flex;
justify-content: space-between;
height: 70px;
align-items: center;
background: #ffffff;
box-shadow: 0px 0px 46px 0px rgba(1, 16, 64, 0.08);
margin-top: 9px;
padding: 0 30px;
.label {
font-size: 16px;
color: #7b7f83;
line-height: 22px;
}
.value {
font-size: 22px;
color: #ff8124;
line-height: 30px;
font-weight: 600;
}
.pay {
width: 200px;
height: 40px;
background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
border-radius: 20px;
font-weight: 500;
font-size: 16px;
color: #ffffff;
line-height: 40px;
text-align: center;
cursor: pointer;
}
}
}
</style>
<script setup>
import { getOrderList, immediatePay, cancelPay } from "./api/index.js";
import qrCodeDialog from "./components/qrCodeDialog.vue";
import { ElMessageBox, ElMessage } from "element-plus";
const status = reactive({
0: {
txt: "待支付",
color: "#F740A6",
bgColor: "#FFE2F2",
},
1: {
txt: "已支付",
color: "#757575",
bgColor: "#DDDDDD",
},
2: {
txt: "未支付",
color: "#34C759",
bgColor: "#D2FFDD",
},
3: {
txt: "已退款",
color: "#FFCC00",
bgColor: "#FFF7D9",
},
});
const order = reactive({
showCodeDialog: false,
qrInfo: {},
pay_loading: false,
pageNo: 1,
pageSize: 10,
total: 0,
data: [],
fetchData() {
getOrderList({ pageNo: order.pageNo, pageSize: order.pageSize }).then(
(res) => {
order.data = res.data.lists;
order.total = res.data.count;
}
);
},
payment(it) {
if (order.pay_loading) return;
order.pay_loading = true;
immediatePay({ orderSn: it.orderSn, payType: 1 })
.then((res) => {
order.qrInfo = res.data;
order.showCodeDialog = true;
})
.finally(() => (order.pay_loading = false));
},
// 取消支付
cancelPayment(it) {
ElMessageBox.confirm("确定取消支付吗?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
draggable: true,
})
.then(() => {
cancelPay({ orderSn: it.orderSn }).then(() => {
order.fetchData();
ElMessage({
type: "success",
message: "操作成功",
});
});
})
.catch(() => {});
},
});
order.fetchData();
</script>
<template>
<div class="container">
<div
v-for="(it, index) in order.data"
:key="index"
@click="
$router.push({
path: '/seat/order_detail',
query: { orderSn: it.orderSn },
})
"
class="order-item"
>
<div class="info_box">
<img class="cover_img" :src="it.coverImg" />
<div class="info">
<div class="title">{{ it.name }}</div>
<div class="common">时间:{{ it.dateStr }}</div>
<div class="common">地址:{{ it.placeName }}</div>
<div class="common">订单编号:{{ it.orderSn }}</div>
<div class="common">张数:{{ it.ticketNum }}</div>
<div class="common">金额:¥{{ it.payAmount }}</div>
<div class="status">
<div class="label">订单状态:</div>
<div class="value">
<div
:style="{
borderColor: status[it.state].color,
background: status[it.state].bgColor,
color: status[it.state].color,
}"
class="tag"
>
{{ status[it.state].txt }}
</div>
<div v-if="it.state == 0" class="tip">
请尽快完成支付,还剩{{ it.min }}{{ it.sec }}
</div>
</div>
</div>
</div>
</div>
<div v-if="it.state == 0" class="btn_box">
<div class="pay" @click.stop="order.payment(it)">立即支付</div>
<div class="can_pay" @click.stop="order.cancelPayment(it)">
取消支付
</div>
</div>
</div>
<qrCodeDialog
:showCodeDialog="order.showCodeDialog"
:qrCode="order.qrInfo?.scanCodeUrl"
/>
<div class="pagination">
<el-pagination
v-show="order.total > 0"
v-model:current-page="order.pageNo"
v-model:page-size="order.pageSize"
background
layout="prev, pager, next"
:total="order.total"
@current-change="order.fetchData()"
/>
</div>
</div>
</template>
<style scoped lang="scss">
.container {
width: 1200px;
margin: 0 auto;
padding: 26px 0;
font-family: SourceHanSansCN, SourceHanSansCN;
.order-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 36px;
background: #fff;
box-shadow: 0px 0px 46px 0px rgba(1, 16, 64, 0.08);
border-radius: 8px;
margin-bottom: 30px;
cursor: pointer;
.info_box {
display: flex;
gap: 20px;
.cover_img {
width: 155px;
height: 200px;
object-fit: fill;
}
.info {
.title {
font-weight: bold;
font-size: 22px;
color: #000000;
line-height: 33px;
margin-bottom: 25px;
margin-bottom: 10px;
}
.common {
font-weight: 500;
font-size: 16px;
color: #4e4e4e;
margin-bottom: 6px;
}
.status {
display: flex;
.label {
font-weight: 500;
font-size: 16px;
color: #4e4e4e;
line-height: 24px;
}
.value {
display: flex;
align-items: center;
gap: 20px;
.tag {
padding: 6px 14px;
border-radius: 6px;
border: 1px solid #34c759;
}
.tip {
font-size: 16px;
color: #f740a6;
line-height: 24px;
}
}
}
}
}
.btn_box {
display: flex;
flex-direction: column;
gap: 12px;
.pay {
width: 175px;
height: 40px;
background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
border-radius: 20px;
font-weight: 500;
font-size: 16px;
color: #ffffff;
line-height: 40px;
text-align: center;
cursor: pointer;
}
.can_pay {
width: 175px;
height: 40px;
background: #fff;
border-radius: 20px;
font-weight: 500;
font-size: 16px;
color: #493ceb;
line-height: 40px;
border: 1px solid #493ceb;
text-align: center;
box-sizing: border-box;
cursor: pointer;
}
}
}
}
.pagination {
display: flex;
justify-content: center;
}
</style>
<script setup>
import { deleteViewPeople, viewPeopleList } from "./api/index.js";
import { ElMessageBox, ElMessage } from "element-plus";
const audience = reactive({
data: [],
fetchData() {
viewPeopleList().then((res) => {
audience.data = res.data;
});
},
deletePeople(id) {
ElMessageBox.confirm("确定删除该观看人吗?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
draggable: true,
})
.then(() => {
deleteViewPeople({ id }).then(() => {
audience.fetchData();
ElMessage({
type: "success",
message: "操作成功",
});
});
})
.catch(() => {});
},
});
audience.fetchData();
</script>
<template>
<div class="container">
<div class="title">
<div
class="add_btn"
@click="$router.push({ path: '/seat/add_watch_people' })"
>
新增
</div>
观影人管理
</div>
<div class="content">
<div class="people_box">
<div
v-for="(it, index) in audience.data"
:key="index"
class="people_item"
>
<div class="name">{{ it.name }}</div>
<div class="idcard">身份证:{{ it.idCard }}</div>
<div class="btn" @click="audience.deletePeople(it.id)">删除</div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
div {
box-sizing: border-box;
}
.container {
padding: 20px 0;
width: 1200px;
margin: 0 auto;
.title {
position: relative;
padding: 11px;
text-align: center;
background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
font-size: 18px;
color: #ffffff;
.add_btn {
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
width: 68px;
height: 24px;
border-radius: 12px;
border: 1px solid #ffffff;
font-weight: 400;
font-size: 12px;
color: #ffffff;
text-align: center;
line-height: 24px;
box-sizing: border-box;
user-select: none;
cursor: pointer;
}
}
.content {
min-height: 590px;
background-color: #fff;
box-shadow: 0px 0px 46px 0px rgba(1, 16, 64, 0.08);
padding: 20px;
.people_box {
display: flex;
flex-wrap: wrap;
gap: 20px;
.people_item {
width: 275px;
height: 137px;
background: #ffffff;
border: 1px solid #dcdfe6;
padding: 16px;
.name {
font-weight: 600;
font-size: 16px;
color: #2d373f;
line-height: 22px;
}
.idcard {
font-size: 16px;
color: #95a1a6;
line-height: 22px;
margin-top: 12px;
margin-bottom: 17px;
}
.btn {
width: 69px;
height: 32px;
background: #e7e6ff;
font-weight: 400;
font-size: 16px;
color: #493ceb;
line-height: 32px;
text-align: center;
cursor: pointer;
user-select: none;
}
}
}
}
}
</style>
<script setup>
import { ElMessage } from "element-plus";
import { getPriceLevelInfo, getSiteConfig } from "./api/index.js";
const route = useRoute();
const router = useRouter();
const iframeRef = ref();
// 获取票档
const price = reactive({
curPriceId: route.query.ticket_block,
data: [],
fetchData() {
getPriceLevelInfo({
actId: route.query?.actId ?? 1,
sessionId: route.query.sessionId,
openType: route.query.openType,
sitePlace: route.query.sitePlace,
ticketType: route.query.ticketType,
}).then((res) => {
this.data = res.data;
// price.curPriceId = route.query.ticket_block
});
},
onClickPrice(e) {
// if (selectedSeats.value?.length) {
// return ElMessage({ type: "warning", message: "请先取消已选座位" });
// }
price.curPriceId = e.priceId;
},
});
// 座位禁用时图标地址
const disabledIconUrl =
"http://114.55.227.212:8083/images/20240511/unselect_default.png";
const siteConfig = reactive({
loading: false,
data: [],
fetchData() {
return getSiteConfig({
actId: route.query.actId ?? 1,
openType: route.query.openType,
sessionId: route.query.sessionId,
sitePlace: route.query.sitePlace,
ticketType: route.query.ticketType,
}).then((res) => {
const gridSize = 40;
const seat_arr = res.data.map((it, index) => {
return {
...it,
// 这几个是iframe引擎渲染座位必须的属性,规定好的
x: gridSize * it.x,
y: gridSize * it.y,
w: gridSize,
h: gridSize,
icon: it.state == 1 ? it.selectIcon : disabledIconUrl, // 图片的url
active: 0, // 是否选中
priceId: route.query.openType == 0 ? it.dayPriceId : it.nightPriceId,
};
});
siteConfig.data = seat_arr;
return seat_arr;
});
},
});
watch(
() => price.curPriceId,
(priceId) => {
siteConfig.data.forEach((it) => {
sendMsg("update-seat", {
id: it.id,
data: {
icon:
it.state == 1 && priceId == it.priceId
? it.active
? it.unSelectIcon
: it.selectIcon
: disabledIconUrl,
},
});
});
console.log("update完成");
},
{ immediate: true }
);
const sendMsg = (type, data) =>
iframeRef.value?.contentWindow.postMessage({ type: type, data: data }, "*");
/**
* 1. 加載iframe 3. 请求API的数据
* 2. 等待iframe里面资源加载完毕并触发picker-ready事件
* 4. 传递数据给iframe,等待渲染
*/
window.addEventListener("message", (e) => {
const data = e.data;
console.log("[parent]", data);
if (data.type == "picker-ready") {
// apiPromise.then(() => {})
siteConfig.fetchData().then((res) => {
const seat_arr = res.map((it) => {
return {
...it,
active: 0,
icon:
it.state == 1 && price.curPriceId == it.priceId
? it.selectIcon
: disabledIconUrl,
};
});
// 子页面加载完毕,这里iframeRef一定ok
iframeRef.value.contentWindow.postMessage(
{
type: "load-seats",
data: seat_arr,
},
"*"
);
});
} else if (data.type == "seat-click") {
// 子页面点击了座位
const seatData = data.data;
console.log("座位点击", seatData);
// 如果座位处于不可点击状态,就return
if (seatData.state != 1) return;
// 如果当前筛选了某种座位,点击的不是这种座位,也返回
if (price.curPriceId && seatData.priceId != price.curPriceId) return;
const newActive = seatData.active == 0 ? 1 : 0;
const siteConfigItem = siteConfig.data.find((it) => it.id == seatData.id);
if (siteConfigItem) {
siteConfigItem.active = newActive;
}
sendMsg("update-seat", {
id: seatData.id,
data: {
active: newActive,
icon: newActive ? seatData.unSelectIcon : seatData.selectIcon,
},
});
}
});
const deleteSiteConfigItem = (seatData) => {
const newActive = seatData.active == 0 ? 1 : 0;
const siteConfigItem = siteConfig.data.find((it) => it.id == seatData.id);
if (siteConfigItem) {
siteConfigItem.active = newActive;
}
sendMsg("update-seat", {
id: seatData.id,
data: { icon: newActive ? seatData.unSelectIcon : seatData.selectIcon },
});
};
/** 所选座位 */
const selectedSeats =
computed(() => siteConfig.data.filter((it) => it.active == 1)) ?? [];
/** 所选座位价格 */
const sumPrice = computed(() => {
return selectedSeats.value.reduce((total, item) => {
const price =
route.query.openType == 0
? Number(item.dayPrice)
: Number(item.nightPrice);
return total + price;
}, 0);
});
const toConfirmOrder = () => {
const seatIds = selectedSeats.value.map((it) => it.id);
if (!seatIds.length)
return ElMessage({ type: "warning", message: "请先选择座位" });
router.push({
path: "/seat/confirm_order",
query: {
openType: route.query.openType,
sessionId: route.query.sessionId,
sitePlace: route.query.sitePlace,
ticketType: route.query.ticketType,
seatIds: seatIds.join(","),
},
});
};
price.fetchData();
</script>
<template>
<div class="container">
<div class="top">
<div class="time">
<span>{{ route.query?.time_txt }}</span>
<span class="place">{{ route.query.sitePlace }}</span>
</div>
<div class="price_tab">
<div
v-for="(it, index) in price.data"
class="tab_item"
:class="{ tabActive: it.priceId == price.curPriceId }"
@click="price.onClickPrice(it)"
>
<img class="seat" :src="it.selectIcon" />
<span class="price">{{ it.price }}¥</span>
</div>
</div>
</div>
<div v-if="selectedSeats?.length" class="bottom">
<div class="seat_box">
<!-- v-for="(it, index) in selectedSeats" -->
<div v-for="(it, index) in selectedSeats" class="seat_item">
<img class="seat_icon" :src="it.selectIcon" />
<span class="num">{{ it.area }}{{ it.pai }}{{ it.no }}</span>
<el-icon
style="cursor: pointer"
color="#ccc"
@click="deleteSiteConfigItem(it)"
><CircleCloseFilled
/></el-icon>
</div>
</div>
<div class="pay">
<div class="sum">¥{{ sumPrice?.toFixed(2) }}</div>
<div class="pay_btn" @click="toConfirmOrder()">立即购买</div>
</div>
</div>
<div class="iframeBox">
<iframe
ref="iframeRef"
class="iframe"
id="iframe"
src="http://seat-choose.parent4relax.com/#/seat-picker"
></iframe>
</div>
</div>
</template>
<style scoped lang="scss">
.container {
width: 1200px;
margin: 0 auto;
padding: 20px;
.top {
width: 100%;
background-color: #fff;
padding: 20px;
margin-bottom: 10px;
border-radius: 6px;
.time {
font-size: 18px;
font-weight: 600;
margin-bottom: 10px;
.place {
color: #7e8489;
margin-left: 15px;
}
}
.price_tab {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
.tabActive {
background: #eeeeee !important;
border: 2px solid #7e8489 !important;
}
.tab_item {
display: flex;
align-items: center;
padding: 10px 14px;
background: #f5f7f8;
border-radius: 30px;
border: 2px solid #dcdedf;
font-size: 16px;
color: #646666;
cursor: pointer;
user-select: none;
.seat {
width: 14px;
height: 14px;
margin-right: 5px;
}
}
}
}
.iframeBox {
border-radius: 6px;
background-color: #fff;
padding: 20px;
margin-bottom: 20px;
}
.iframe {
width: 100%;
height: 500px;
border: none;
background-color: #f7f8fa;
}
.bottom {
border-radius: 6px;
background-color: #fff;
padding: 20px;
margin-bottom: 20px;
.seat_box {
display: flex;
flex-wrap: wrap;
gap: 10px;
width: 100%;
.seat_item {
display: flex;
align-items: center;
padding: 10px 14px;
font-size: 16px;
color: #29343c;
background: #eeeeee;
border-radius: 30px;
border: 2px solid #7e8489;
user-select: none;
.seat_icon {
width: 14px;
height: 14px;
margin-right: 5px;
}
.num {
margin-right: 5px;
}
}
}
.pay {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
.sum {
font-weight: 600;
font-size: 22px;
color: #493ceb;
}
.pay_btn {
width: 200px;
height: 40px;
background: #493ceb;
border-radius: 20px;
margin-top: 10px;
font-size: 14px;
color: #fff;
line-height: 40px;
text-align: center;
cursor: pointer;
font-weight: 600;
user-select: none;
}
}
}
}
</style>
<script setup></script>
<template>
<div class="view">
<router-view />
</div>
</template>
<style scoped lang="scss">
.view {
min-width: 1024px;
max-width: 1920px;
margin: 0 auto;
}
</style>
/** 用户登录token储存的key */
export const TOKEN_KEY = "SEAT_TOKEN";
/** 设置token */
export const setToken = (token) => localStorage.setItem(TOKEN_KEY, token);
/**
* 获取登录token
* @param drop 是否清空
*/
export const getToken = (drop = false) => {
let token = localStorage.getItem(TOKEN_KEY);
if (!token) return null;
if (drop) localStorage.removeItem(TOKEN_KEY);
return token;
};
// http.js
import axios from "axios";
import { getToken } from "./local-store";
import { ElMessage } from "element-plus";
const baseURL = "http://101.43.15.205:8084"; //"http://book.xiaojinyu.games"; // 这里填入你的基础 API URL
const timeout = 15000; // 请求超时时间
const http = axios.create({
baseURL,
timeout,
headers: {
"Content-Type": "application/json",
},
});
// 请求拦截器
http.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么
const TOKEN = getToken();
config.headers.Authorization = TOKEN;
if (config.method == "get") config.params = config.data;
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
http.interceptors.response.use(
(response) => {
// 判断是否有异常
let error = null; // 若无异常此值为null
if (response.status !== 200) {
error = Error(`Request failed with statuCode ${response.status}`);
}
if (response.data.code != 200) {
return ElMessage({ type: "error", message: response.data.msg });
}
return response.data;
},
(error) => {
// 对响应错误做点什么
return Promise.reject(error);
}
);
// 封装请求函数
const request = (method, url, data = null) => {
return http({
method,
url,
data,
});
};
export default request;
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!