4202d307 by 杨炀

Merge commit '547289a7' into order

2 parents a586c361 547289a7
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
30 "jszip": "^3.10.1", 30 "jszip": "^3.10.1",
31 "katex": "^0.16.6", 31 "katex": "^0.16.6",
32 "lodash": "^4.17.21", 32 "lodash": "^4.17.21",
33 "md5js": "^1.0.7",
33 "nprogress": "0.2.0", 34 "nprogress": "0.2.0",
34 "pinia": "2.0.35", 35 "pinia": "2.0.35",
35 "qrcode": "^1.5.3", 36 "qrcode": "^1.5.3",
......
This diff could not be displayed because it is too large.
...@@ -13,6 +13,7 @@ NProgress.configure({ showSpinner: false }) ...@@ -13,6 +13,7 @@ NProgress.configure({ showSpinner: false })
13 const whiteList = ['/login', '/register', '/regulations'] 13 const whiteList = ['/login', '/register', '/regulations']
14 14
15 router.beforeEach((to, from, next) => { 15 router.beforeEach((to, from, next) => {
16 console.log('to2', to)
16 NProgress.start() 17 NProgress.start()
17 if (getToken()) { 18 if (getToken()) {
18 // debugger 19 // debugger
......
...@@ -9,6 +9,7 @@ import useUserStore from '@/store/modules/user' ...@@ -9,6 +9,7 @@ import useUserStore from '@/store/modules/user'
9 NProgress.configure({ showSpinner: false }) 9 NProgress.configure({ showSpinner: false })
10 10
11 router.beforeEach((to, from, next) => { 11 router.beforeEach((to, from, next) => {
12 console.log('to', to)
12 NProgress.start() 13 NProgress.start()
13 if (getToken()) { 14 if (getToken()) {
14 // 判断当前用户是否已拉取完user_info信息 15 // 判断当前用户是否已拉取完user_info信息
......
...@@ -500,6 +500,59 @@ export const constantRoutes = [ ...@@ -500,6 +500,59 @@ export const constantRoutes = [
500 meta: { title: 'System messages' } 500 meta: { title: 'System messages' }
501 } 501 }
502 ] 502 ]
503 },
504 {
505 path: 'seat',
506 component: () => import('@/viewsPc/seat/seat'),
507 name: 'seat',
508 redirect: '/seat/detail',
509 children: [
510 {
511 path: 'detail',
512 name: 'seat_detail',
513 component: () => import('@/viewsPc/seat/ticket-detail'),
514 meta: { title: '购票详情' },
515 props: route => ({
516 activityId:route.query.id,
517 })
518 },
519 {
520 path: 'seat_picker',
521 name: 'seat_picker',
522 component: () => import('@/viewsPc/seat/seat-picker'),
523 meta: { title: '选座' }
524 },
525 {
526 path: 'order',
527 name: 'seat_order',
528 component: () => import('@/viewsPc/seat/order-list'),
529 meta: { title: '我的订单' }
530 },
531 {
532 path: 'order_detail',
533 name: 'order_detail',
534 component: () => import('@/viewsPc/seat/order-detail'),
535 meta: { title: '订单详情' }
536 },
537 {
538 path: 'confirm_order',
539 name: 'confirm_order',
540 component: () => import('@/viewsPc/seat/confirm-order'),
541 meta: { title: '确认订单' }
542 },
543 {
544 path: 'add_watch_people',
545 name: 'add_watch_people',
546 component: () => import('@/viewsPc/seat/add-watch-people'),
547 meta: { title: '新增观影人' }
548 },
549 {
550 path: 'people_manage',
551 name: 'people_manage',
552 component: () => import('@/viewsPc/seat/people-manage'),
553 meta: { title: '观影人管理' }
554 },
555 ]
503 } 556 }
504 ] 557 ]
505 }, 558 },
......
...@@ -196,7 +196,7 @@ function submit() { ...@@ -196,7 +196,7 @@ function submit() {
196 return 196 return
197 } 197 }
198 198
199 if (!timeVal.value.id)return proxy.$modal.msgError('请选择预约时间!', ) 199 if (!timeVal.value.id)return proxy.$modal.msgError( language.value==0?'请选择预约时间!':"Please select an appointment time!" )
200 200
201 proxy.$refs['formRef'].validate(valid=>{ 201 proxy.$refs['formRef'].validate(valid=>{
202 if (valid){ 202 if (valid){
......
1 <script setup>
2 import { ElMessage } from "element-plus";
3 import { addViewPeople } from "./api/index.js";
4
5 const router = useRouter();
6
7 const people = reactive({
8 form: {
9 name: "",
10 idCard: "",
11 },
12 type: "身份证",
13 onConfirm() {
14 if (!people.form.name)
15 return ElMessage({ type: "warning", message: "请输入姓名" });
16 if (!people.form.idCard)
17 return ElMessage({ type: "warning", message: "请输入证件号" });
18
19 // 使用正则验证身份证号码格式
20 const idCardRegex =
21 /^[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]$/;
22 if (!idCardRegex.test(people.form.idCard))
23 return ElMessage({ type: "warning", message: "身份证号格式不正确" });
24
25 addViewPeople(people.form).then((res) => {
26 ElMessage({ type: "success", message: "操作成功" });
27 router.go(-2);
28 });
29 },
30 });
31 </script>
32
33 <template>
34 <div class="container">
35 <div class="title">新增观影人</div>
36 <div class="content">
37 <div class="form-item">
38 <div>
39 <div class="label">姓名</div>
40 <el-input
41 v-model="people.form.name"
42 style="width: 570px"
43 placeholder="请输入姓名"
44 />
45 </div>
46 <div>
47 <div class="label">证件类型</div>
48 <el-input
49 v-model="people.type"
50 style="width: 570px"
51 placeholder="Please input"
52 readonly
53 />
54 </div>
55 </div>
56 <div class="form-item">
57 <div>
58 <div class="label">身份证号</div>
59 <el-input
60 v-model="people.form.idCard"
61 style="width: 570px"
62 placeholder="请输入身份证号"
63 />
64 </div>
65 </div>
66 </div>
67
68 <div class="footer">
69 <div class="can_pay">取消</div>
70 <div class="pay" @click="people.onConfirm()">确认</div>
71 </div>
72 </div>
73 </template>
74
75 <style scoped lang="scss">
76 div {
77 box-sizing: border-box;
78 }
79 .container {
80 padding: 20px 0;
81 width: 1200px;
82 margin: 0 auto;
83
84 .title {
85 padding: 11px;
86 text-align: center;
87 background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
88 font-size: 18px;
89 color: #ffffff;
90 }
91
92 .content {
93 padding: 46px 20px 24px;
94 background-color: #fff;
95 :deep(.el-input) {
96 height: 48px;
97 border-radius: 24px !important;
98 }
99 .form-item {
100 display: flex;
101 gap: 20px;
102 margin-bottom: 36px;
103 &:last-child {
104 margin: 0;
105 }
106 .label {
107 font-size: 18px;
108 color: #333333;
109 margin-bottom: 16px;
110 }
111 }
112 }
113 .footer {
114 display: flex;
115 justify-content: center;
116 align-items: center;
117 gap: 20px;
118 height: 70px;
119 background-color: #fff;
120 margin-top: 12px;
121 .pay {
122 width: 200px;
123 height: 40px;
124 background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
125 border-radius: 20px;
126 font-weight: 500;
127 font-size: 16px;
128 color: #ffffff;
129 line-height: 40px;
130 text-align: center;
131 cursor: pointer;
132 }
133 .can_pay {
134 width: 200px;
135 height: 40px;
136 background: #f6f6f6;
137 border-radius: 20px;
138 font-weight: 500;
139 font-size: 16px;
140 color: #999;
141 line-height: 40px;
142 text-align: center;
143 box-sizing: border-box;
144 cursor: pointer;
145 }
146 }
147 }
148 </style>
1 import request from "../utils/request";
2
3 export const loginFree = (data) => request("POST", "/login/loginFree", data);
4 /** 活动详情 */
5 export const activityDetail = (data) =>
6 request("GET", `/api/activity/detail/${data.actId}`, data);
7 /** 场次详情 */
8 export const sessionDetail = (data) =>
9 request("GET", `/api/activity/sessionDetail/${data.actId}`, data);
10 /** 获取场馆信息 */
11 export const getSitePlaceInfo = (data) =>
12 request("GET", `/api/activity/getSitePlaceInfo`, data);
13 /** 获取票档信息 */
14 export const getPriceLevelInfo = (data) =>
15 request("GET", `/api/activity/getPriceLevelInfo`, data);
16 /** 获取座位信息 */
17 export const getSiteConfig = (data) =>
18 request("GET", `/api/activity/getSiteConfig`, data);
19 /** 确认订单 */
20 export const confirmOrder = (data) =>
21 request("POST", `/api/order/confirmOrder`, data);
22 /** 订单支付 */
23 export const payOrder = (data) =>
24 request("POST", `/api/order/payment`, data);
25 /** 观众列表 */
26 export const viewPeopleList = (data) =>
27 request("GET", `/api/customer/list`, data);
28 /** 删除观众 */
29 export const deleteViewPeople = (data) =>
30 request("POST", `/api/customer/delete/${data.id}`, data);
31 /** 新增观众 */
32 export const addViewPeople = (data) =>
33 request("POST", `/api/customer/add`, data);
34 /** 订单列表 */
35 export const getOrderList = (data) =>
36 request("GET", `/api/order/list`, data);
37 /** 立即支付 */
38 export const immediatePay = (data) =>
39 request("POST", `/api/order/immediatePay`, data);
40 /** 取消支付 */
41 export const cancelPay = (data) =>
42 request("POST", `/api/order/cancelPay/${data.orderSn}`, data);
43 /** 退单 */
44 export const cancelOrder = (data) =>
45 request("POST", `/api/order/cancelOrder/${data.orderSn}`, data);
46 /** 订单详情 */
47 export const getOrderDetail = (data) =>
48 request("GET", `/api/order/detail/${data.orderSn}`, data);
1 <script setup>
2 const props = defineProps({
3 showCodeDialog: {
4 type: Boolean,
5 default: false,
6 },
7 qrCode: {
8 type: String,
9 default: ''
10 }
11 });
12 </script>
13
14 <template>
15 <div>
16 <el-dialog v-model="props.showCodeDialog" title="支付" width="300">
17 <div>
18 <img class="qrcode" :src="props.qrCode" />
19 </div>
20 </el-dialog>
21 </div>
22 </template>
23
24 <style scoped lang="scss">
25 .qrcode {
26 width: 150px;
27 height: 150px;
28 background-color: #8623fc;
29 margin: 0 auto;
30 }
31 </style>
1 <script setup>
2 import { confirmOrder } from "./api/index.js";
3 import { ElMessage } from "element-plus";
4 import { payOrder, viewPeopleList } from "./api/index.js";
5 import { reactive } from "vue";
6 import qrCodeDialog from "./components/qrCodeDialog.vue";
7
8 const route = useRoute();
9 const router = useRouter();
10
11 const payment = reactive({
12 showCodeDialog: false,
13 btn_loading: false,
14 form: {
15 viewers: [],
16 phone: "",
17 },
18 qrInfo: {},
19 paymentHandle() {
20 if (payment.form.viewers.length != order.data?.seatInfo?.length)
21 return ElMessage({ type: "warning", message: "观看人与购买票数不符" });
22 if (!payment.form.phone)
23 return ElMessage({ type: "warning", message: "请输入联系人电话" });
24 payOrder({
25 contactPhone: payment.form.phone,
26 customerIds: payment.form.viewers,
27 orderToken: order.data?.orderToken,
28 payType: 1,
29 paymentAmount: order.data?.paymentAmount,
30 }).then((res) => {
31 // TODO: 这里有一个二维码
32 payment.qrInfo = res.data;
33 payment.showCodeDialog = true;
34 router.push({
35 path: "/seat/order",
36 });
37 });
38 },
39 });
40
41 const order = reactive({
42 data: null,
43 fetchData() {
44 confirmOrder({
45 actId: route.query.actId ?? 1,
46 openType: route.query.openType,
47 sessionId: route.query.sessionId,
48 sitePlace: route.query.sitePlace,
49 ticketType: route.query.ticketType,
50 seatIds: route.query.seatIds.split(","),
51 }).then((res) => {
52 this.data = res.data;
53 });
54 },
55 });
56
57 const audience = reactive({
58 data: [],
59 fetchData() {
60 viewPeopleList().then((res) => {
61 audience.data = res.data;
62 });
63 },
64 });
65
66 audience.fetchData();
67 order.fetchData();
68 </script>
69
70 <template>
71 <div class="container">
72 <div class="title">订单确认</div>
73 <div class="content">
74 <div class="left">
75 <div class="info">
76 <div class="name">{{ order.data?.activityName }}</div>
77 <div class="address">{{ order.data?.placeName }}</div>
78 </div>
79
80 <div class="ticket_info">
81 <div class="tit_box">
82 <div class="line"></div>
83 <div class="txt">订票信息</div>
84 </div>
85
86 <div class="form">
87 <el-form>
88 <el-form-item label="联系人">
89 <el-input
90 v-model="payment.form.phone"
91 placeholder="请输入联系人电话"
92 style="width: 260px"
93 />
94 </el-form-item>
95 <el-form-item label="观看人">
96 <div class="p_box">
97 <div class="people">
98 <el-checkbox-group
99 v-model="payment.form.viewers"
100 :max="order.data?.seatInfo?.length"
101 >
102 <div
103 v-for="(it, index) in audience.data"
104 :key="index"
105 class="prople_item"
106 >
107 <div>
108 <div class="name">{{ it.name }}</div>
109 <div class="idcard">{{ it.idCard }}</div>
110 </div>
111 <el-checkbox :value="it.id"> </el-checkbox>
112 </div>
113 </el-checkbox-group>
114 </div>
115 <!-- button -->
116 <div
117 class="btn"
118 @click="$router.push({ path: '/seat/people_manage' })"
119 >
120 新增
121 </div>
122 </div>
123 </el-form-item>
124 </el-form>
125 </div>
126 </div>
127 </div>
128
129 <div class="right">
130 <div class="tit_box">
131 <div class="line"></div>
132 <div class="txt">订单明细</div>
133 </div>
134
135 <div class="detail">
136 <div class="detail_top">
137 <div class="time">{{ order.data?.dateStr }}</div>
138 <div class="ticket">
139 {{ order.data?.singlePrice }}元票档 x{{
140 order.data?.seatInfo?.length
141 }}
142 </div>
143 </div>
144 <div class="detail_center">
145 <div
146 v-for="(it, index) in order.data?.seatInfo"
147 :key="index"
148 class="ticket"
149 >
150 <span v-if="it.venueId == 1">{{ it.area }}</span>
151 {{ it.pai }}{{ it.no }}座 ({{
152 it.venueId == 1 ? "B6" : "B4"
153 }}馆)
154 </div>
155 </div>
156 <div class="detail_b">
157 <div class="sum_txt">共计</div>
158 <div class="price_num">¥{{ order.data?.paymentAmount }}</div>
159 </div>
160 </div>
161 </div>
162 </div>
163 <div class="footer">
164 <div>
165 <span class="label">共计金额:</span><span class="value">¥900.00</span>
166 </div>
167 <div class="pay" @click="payment.paymentHandle()">立即支付</div>
168 </div>
169
170 <qrCodeDialog
171 :showCodeDialog="payment.showCodeDialog"
172 :qrCode="payment.qrInfo?.scanCodeUrl"
173 />
174 </div>
175 </template>
176
177 <style scoped lang="scss">
178 div {
179 box-sizing: border-box;
180 }
181 .qrcode {
182 width: 150px;
183 height: 150px;
184 background-color: #8623fc;
185 margin: 0 auto;
186 }
187 .container {
188 padding: 20px 0;
189 width: 1200px;
190 margin: 0 auto;
191
192 .title {
193 padding: 11px;
194 text-align: center;
195 background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
196 font-size: 18px;
197 color: #ffffff;
198 }
199
200 .content {
201 display: flex;
202 background-color: #fff;
203 padding: 20px 0;
204 }
205
206 .line {
207 width: 4px;
208 height: 18px;
209 background: linear-gradient(180deg, #493ceb 0%, #8623fc 100%);
210 border-radius: 4px;
211 }
212
213 .left {
214 padding-left: 20px;
215 .info {
216 width: 640px;
217 background: rgba(69, 61, 234, 0.04);
218 border-radius: 8px;
219 border: 1px solid #d3d1f6;
220 padding: 20px 0 28px 33px;
221 margin-bottom: 20px;
222 .name {
223 font-weight: 500;
224 font-size: 18px;
225 color: #000000;
226 margin-bottom: 20px;
227 }
228 .address {
229 font-weight: 400;
230 font-size: 14px;
231 color: #929aa0;
232 }
233 }
234
235 .ticket_info {
236 .tit_box {
237 display: flex;
238 align-items: center;
239 gap: 10px;
240 margin-bottom: 14px;
241 .txt {
242 font-weight: bold;
243 font-size: 16px;
244 color: #493ceb;
245 }
246 }
247 .form {
248 width: 640px;
249 min-height: 464px;
250 padding: 20px 60px;
251 border-radius: 5px;
252 border: 1px solid #dcdfe6;
253
254 .p_box {
255 display: flex;
256 gap: 10px;
257 .people {
258 width: 298px;
259 background: #fbfcfd;
260 border-radius: 2px;
261 border: 1px solid #dcdfe6;
262 padding: 0 14px;
263 .prople_item {
264 display: flex;
265 justify-content: space-between;
266 align-items: center;
267 padding: 14px 0;
268 border-bottom: 1px solid #dcdfe6;
269 &:last-child {
270 border: none;
271 }
272 .name {
273 font-size: 16px;
274 color: #929aa0;
275 margin-bottom: 20px;
276 }
277 .idcard {
278 font-size: 10px;
279 color: #929aa0;
280 }
281 }
282 }
283 .btn {
284 width: 90px;
285 height: 40px;
286 background: #fbfcfd;
287 border-radius: 20px;
288 border: 1px solid #493ceb;
289 margin-top: 10px;
290 font-size: 14px;
291 color: #493ceb;
292 line-height: 40px;
293 text-align: center;
294 cursor: pointer;
295 user-select: none;
296 }
297 }
298 }
299 }
300 }
301
302 .right {
303 width: 460px;
304 margin-left: 36px;
305 .tit_box {
306 display: flex;
307 align-items: center;
308 gap: 10px;
309 margin-bottom: 20px;
310 .txt {
311 font-weight: bold;
312 font-size: 16px;
313 color: #493ceb;
314 }
315 }
316
317 .detail {
318 padding: 15px 26px;
319 border-radius: 5px;
320 border: 1px solid #dcdfe6;
321 .detail_top {
322 padding-bottom: 13px;
323 border-bottom: 1px solid #dcdfe6;
324 .time {
325 font-weight: 500;
326 font-size: 18px;
327 color: #2d373f;
328 line-height: 25px;
329 margin-bottom: 8px;
330 }
331 .ticket {
332 font-size: 16px;
333 color: #2d373f;
334 }
335 }
336 .detail_center {
337 margin-top: 14px;
338 display: flex;
339 flex-direction: column;
340 padding-bottom: 13px;
341 border-bottom: 1px solid #dcdfe6;
342 gap: 8px;
343 .ticket {
344 font-size: 16px;
345 color: #2d373f;
346 }
347 }
348 .detail_b {
349 display: flex;
350 justify-content: space-between;
351 align-items: center;
352 margin-top: 16px;
353 .sum_txt {
354 font-weight: 600;
355 font-size: 18px;
356 color: #2d373f;
357 line-height: 25px;
358 }
359 .price_num {
360 font-weight: 600;
361 font-size: 36px;
362 color: #ff8124;
363 line-height: 50px;
364 }
365 }
366 }
367 }
368
369 .footer {
370 display: flex;
371 justify-content: space-between;
372 height: 70px;
373 align-items: center;
374 background: #ffffff;
375 box-shadow: 0px 0px 46px 0px rgba(1, 16, 64, 0.08);
376 margin-top: 9px;
377 padding: 0 30px;
378 .label {
379 font-size: 16px;
380 color: #7b7f83;
381 line-height: 22px;
382 }
383 .value {
384 font-size: 22px;
385 color: #ff8124;
386 line-height: 30px;
387 font-weight: 600;
388 }
389 .pay {
390 width: 200px;
391 height: 40px;
392 background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
393 border-radius: 20px;
394 font-weight: 500;
395 font-size: 16px;
396 color: #ffffff;
397 line-height: 40px;
398 text-align: center;
399 cursor: pointer;
400 }
401 }
402 }
403 </style>
1 <script setup>
2 import { reactive } from "vue";
3 import {
4 cancelOrder,
5 getOrderDetail,
6 immediatePay,
7 cancelPay,
8 } from "./api/index.js";
9 import qrCodeDialog from "./components/qrCodeDialog.vue";
10 import { ElMessageBox, ElMessage } from "element-plus";
11
12 const route = useRoute();
13 const router = useRouter();
14
15 const status = reactive({
16 0: {
17 txt: "待支付",
18 color: "#F740A6",
19 bgColor: "#FFE2F2",
20 },
21 1: {
22 txt: "已支付",
23 color: "#757575",
24 bgColor: "#DDDDDD",
25 },
26 2: {
27 txt: "未支付",
28 color: "#34C759",
29 bgColor: "#D2FFDD",
30 },
31 3: {
32 txt: "已退款",
33 color: "#FFCC00",
34 bgColor: "#FFF7D9",
35 },
36 });
37
38 const detail = reactive({
39 showCodeDialog: false,
40 pay_loading: false,
41 qrInfo: {},
42 data: null,
43 timer: null,
44
45 // 分钟
46 minutes: 0,
47 seconds: 0,
48 fetchData() {
49 getOrderDetail({ orderSn: route.query.orderSn }).then((res) => {
50 detail.data = res.data;
51 detail.countDown(detail.data?.payEndTime);
52 });
53 },
54 // 倒计时
55 countDown(time) {
56 // 当前时间
57 let nowTime = new Date();
58 let endTime = new Date(time);
59 // 两个日期相差的时间戳,以毫秒为单位(1000ms = 1s)
60 let totalTime = endTime - nowTime;
61 // 结束时间大于现在的时间
62 if (totalTime > 0) {
63 detail.timer = setInterval(() => {
64 if (totalTime >= 0) {
65 //获取分钟数
66 let minutes = Math.floor(
67 (((totalTime % (3600 * 24 * 1000)) / 1000) % 3600) / 60
68 );
69 //获取秒数
70 let seconds = Math.floor(
71 (((totalTime % (3600 * 24 * 1000)) / 1000) % 3600) % 60
72 )
73 .toString()
74 .padStart(2, "0");
75
76 detail.minutes = minutes;
77 detail.seconds = seconds;
78
79 totalTime -= 1000;
80 // console.log(totalTime)
81 } else {
82 clearInterval(timer); // 停止调用函数
83 }
84 }, 1000);
85 }
86 },
87 payment() {
88 if (detail.pay_loading) return;
89 detail.pay_loading = true;
90 immediatePay({ orderSn: detail.data.orderSn, payType: 1 })
91 .then((res) => {
92 detail.qrInfo = res.data;
93 detail.showCodeDialog = true;
94 })
95 .finally(() => (detail.pay_loading = false));
96 },
97 // 取消支付
98 cancelPay() {
99 ElMessageBox.confirm("确定取消支付吗?", "提示", {
100 confirmButtonText: "确认",
101 cancelButtonText: "取消",
102 type: "warning",
103 draggable: true,
104 })
105 .then(() => {
106 cancelPay({ orderSn: detail.data.orderSn }).then(() => {
107 detail.fetchData();
108 ElMessage({
109 type: "success",
110 message: "操作成功",
111 });
112 });
113 })
114 .catch(() => {});
115 },
116 // 取消购票
117 cancelOrder() {
118 ElMessageBox.confirm("确定取消购票吗?", "提示", {
119 confirmButtonText: "确认",
120 cancelButtonText: "取消",
121 type: "warning",
122 draggable: true,
123 })
124 .then(() => {
125 cancelOrder({ orderSn: detail.data.orderSn }).then((res) => {
126 detail.fetchData();
127 ElMessage({
128 type: "success",
129 message: "操作成功",
130 });
131 });
132 })
133 .catch(() => {});
134 },
135 });
136
137 detail.fetchData();
138 </script>
139
140 <template>
141 <div class="container">
142 <div class="left">
143 <!-- 票务信息 -->
144 <div class="ticket">
145 <div class="th">
146 <div style="width: 33%" class="td">票务信息</div>
147 <div style="width: 25%" class="td">地点</div>
148 <div style="width: 20%" class="td">单价</div>
149 <div style="width: 10%" class="td">数量</div>
150 <div style="width: 12%; text-align: right" class="td">小计</div>
151 </div>
152 <div class="line"></div>
153 <div class="tr">
154 <div style="width: 30%" class="td">{{ detail.data?.name }}</div>
155 <div style="width: 25%" class="td">{{ detail.data?.placeName }}</div>
156 <div style="width: 20%" class="td">
157 ¥{{ detail.data?.singlePrice }}
158 </div>
159 <div style="width: 12%" class="td">
160 x{{ detail.data?.seatList?.length }}
161 </div>
162 <div style="width: 13%; text-align: right" class="td">
163 ¥{{ detail.data?.payAmount }}
164 </div>
165 </div>
166 </div>
167 <!-- 座位 -->
168 <div class="seat_box">
169 <div class="th">
170 <div style="width: 30.33%" class="td">时间座位</div>
171 <div style="width: 30.33%" class="td">订单信息</div>
172 <div style="width: 30.33%" class="td">联系方式</div>
173 </div>
174 <div class="tr">
175 <div style="width: 30.33%" class="td flex-col">
176 <div>{{ detail.data?.dateStr }}</div>
177 <div v-for="(it, index) in detail.data?.seatList" :key="index">
178 <span v-if="it.venueId == 1">{{ it.area }}</span
179 >{{ it.pai }}{{ it.no }}座 ({{
180 it.venueId == 1 ? "B6" : "B4"
181 }}馆)
182 </div>
183 </div>
184 <div style="width: 30.33%" class="td flex-col">
185 <div>订单编号:{{ detail.data?.orderSn }}</div>
186 <div>创建时间:{{ detail.data?.orderTime }}</div>
187 </div>
188 <div style="width: 30.33%" class="td">
189 <div>联系电话:{{ detail.data?.contactPhone }}</div>
190 </div>
191 </div>
192 </div>
193 <!-- 购票人 -->
194 <div class="pay_ticket">
195 <div class="title">购票人</div>
196 <div class="people">
197 <div
198 v-for="(it, index) in detail.data?.customerList"
199 :key="index"
200 class="p_info"
201 >
202 <div>{{ it.name }}</div>
203 <div class="idcard">身份证:{{ it.idCard }}</div>
204 </div>
205 </div>
206 </div>
207 </div>
208
209 <div class="right">
210 <div class="balance">
211 <div class="title">结算信息</div>
212 <div class="cell">
213 <div class="label">订单状态</div>
214 <div class="value">{{ status[detail.data?.state]?.txt }}</div>
215 </div>
216 <div class="cell">
217 <div class="label">订单金额</div>
218 <div class="value">¥{{ detail.data?.payAmount }}</div>
219 </div>
220 <!-- button -->
221 <div v-if="detail.data?.state == 0" class="btn_box">
222 <div class="can_pay" @click="detail.cancelPay()">取消支付</div>
223 <div class="pay" @click="detail.payment()">立即支付</div>
224 </div>
225 <div v-else>
226 <div
227 v-if="detail.data?.state == 1 && detail.data?.isRefund"
228 class="btn_box"
229 >
230 <div class="can_pay" @click="detail.cancelOrder()">取消购票</div>
231 <div
232 class="pay"
233 @click="$router.push({ path: '/seat/seat-picker' })"
234 >
235 再来一单
236 </div>
237 </div>
238
239 <div v-else class="btn_box">
240 <div class="pay_dis">请联系工作人员</div>
241 </div>
242 </div>
243 </div>
244 <div v-if="detail.data?.state == 0" class="tip">
245 请尽快完成支付,还剩{{ detail.minutes }}{{ detail.seconds }}
246 </div>
247 </div>
248
249 <qrCodeDialog
250 :showCodeDialog="detail.showCodeDialog"
251 :qrCode="detail.qrInfo?.scanCodeUrl"
252 />
253 </div>
254 </template>
255
256 <style scoped lang="scss">
257 .container {
258 width: 1200px;
259 margin: 0 auto;
260 padding: 20px 0;
261 display: flex;
262 gap: 20px;
263 .left {
264 width: 780px;
265 // 票务信息
266 .ticket {
267 background-color: #fff;
268 padding: 0 20px;
269 .th {
270 display: flex;
271 justify-content: space-between;
272 padding: 20px 0;
273 .td {
274 font-weight: bold;
275 font-size: 16px;
276 color: #333333;
277 line-height: 24px;
278 }
279 }
280 .line {
281 width: 740px;
282 height: 1px;
283 background: #eee;
284 }
285 .tr {
286 display: flex;
287 justify-content: space-between;
288 padding: 20px 0;
289 .td {
290 font-weight: 400;
291 font-size: 16px;
292 color: #333333;
293 line-height: 24px;
294 }
295 }
296 }
297 // 座位
298 .seat_box {
299 background-color: #fff;
300 padding: 0 20px;
301 margin-top: 20px;
302 .th {
303 display: flex;
304 justify-content: space-between;
305 padding: 20px 0;
306 .td {
307 font-weight: bold;
308 font-size: 16px;
309 color: #333333;
310 line-height: 24px;
311 }
312 }
313 .tr {
314 display: flex;
315 justify-content: space-between;
316 padding: 20px 0;
317 .td {
318 font-weight: 400;
319 font-size: 16px;
320 color: #333333;
321 line-height: 24px;
322 }
323 .flex-col {
324 display: flex;
325 flex-direction: column;
326 gap: 16px;
327 }
328 }
329 }
330 // 购票人
331 .pay_ticket {
332 background-color: #fff;
333 padding: 20px;
334 margin-top: 20px;
335 .title {
336 font-weight: bold;
337 font-size: 16px;
338 color: #333333;
339 margin-bottom: 28px;
340 }
341 .people {
342 display: flex;
343 justify-content: space-between;
344 flex-wrap: wrap;
345 gap: 15px 50px;
346 .p_info {
347 font-weight: 400;
348 font-size: 16px;
349 color: #333333;
350 line-height: 24px;
351 .idcard {
352 color: #999999;
353 }
354 }
355 }
356 }
357 }
358 .right {
359 width: 400px;
360 .balance {
361 background-color: #fff;
362 padding: 20px;
363 .title {
364 font-weight: bold;
365 font-size: 20px;
366 color: #333333;
367 line-height: 30px;
368 margin-bottom: 28px;
369 }
370 .cell {
371 display: flex;
372 justify-content: space-between;
373 align-items: center;
374 margin-bottom: 20px;
375 &:last-child {
376 margin: 0;
377 }
378 .label {
379 font-weight: 400;
380 font-size: 16px;
381 color: #333333;
382 }
383 .value {
384 font-weight: 400;
385 font-size: 16px;
386 color: #ff8124;
387 }
388 }
389 .btn_box {
390 border-top: 1px solid #eee;
391 padding-top: 20px;
392 display: flex;
393 gap: 20px;
394 user-select: none;
395 .pay_dis {
396 width: 360px;
397 height: 40px;
398 background: #a09dff;
399 border-radius: 20px;
400 font-weight: 500;
401 font-size: 16px;
402 color: #ffffff;
403 line-height: 40px;
404 text-align: center;
405 cursor: pointer;
406 }
407 .pay {
408 width: 170px;
409 height: 40px;
410 background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
411 border-radius: 20px;
412 font-weight: 500;
413 font-size: 16px;
414 color: #ffffff;
415 line-height: 40px;
416 text-align: center;
417 cursor: pointer;
418 }
419 .can_pay {
420 width: 170px;
421 height: 40px;
422 background: #f6f6f6;
423 border-radius: 20px;
424 font-weight: 500;
425 font-size: 16px;
426 color: #999;
427 line-height: 40px;
428 text-align: center;
429 box-sizing: border-box;
430 cursor: pointer;
431 }
432 }
433 }
434 .tip {
435 font-weight: 400;
436 font-size: 16px;
437 color: #ea3d6b;
438 line-height: 24px;
439 margin-top: 20px;
440 text-align: center;
441 }
442 }
443 }
444 </style>
1 <script setup>
2 import { getOrderList, immediatePay, cancelPay } from "./api/index.js";
3 import qrCodeDialog from "./components/qrCodeDialog.vue";
4 import { ElMessageBox, ElMessage } from "element-plus";
5
6 const status = reactive({
7 0: {
8 txt: "待支付",
9 color: "#F740A6",
10 bgColor: "#FFE2F2",
11 },
12 1: {
13 txt: "已支付",
14 color: "#757575",
15 bgColor: "#DDDDDD",
16 },
17 2: {
18 txt: "未支付",
19 color: "#34C759",
20 bgColor: "#D2FFDD",
21 },
22 3: {
23 txt: "已退款",
24 color: "#FFCC00",
25 bgColor: "#FFF7D9",
26 },
27 });
28
29 const order = reactive({
30 showCodeDialog: false,
31 qrInfo: {},
32 pay_loading: false,
33 pageNo: 1,
34 pageSize: 10,
35 total: 0,
36 data: [],
37 fetchData() {
38 getOrderList({ pageNo: order.pageNo, pageSize: order.pageSize }).then(
39 (res) => {
40 order.data = res.data.lists;
41 order.total = res.data.count;
42 }
43 );
44 },
45 payment(it) {
46 if (order.pay_loading) return;
47 order.pay_loading = true;
48 immediatePay({ orderSn: it.orderSn, payType: 1 })
49 .then((res) => {
50 order.qrInfo = res.data;
51 order.showCodeDialog = true;
52 })
53 .finally(() => (order.pay_loading = false));
54 },
55 // 取消支付
56 cancelPayment(it) {
57 ElMessageBox.confirm("确定取消支付吗?", "提示", {
58 confirmButtonText: "确认",
59 cancelButtonText: "取消",
60 type: "warning",
61 draggable: true,
62 })
63 .then(() => {
64 cancelPay({ orderSn: it.orderSn }).then(() => {
65 order.fetchData();
66 ElMessage({
67 type: "success",
68 message: "操作成功",
69 });
70 });
71 })
72 .catch(() => {});
73 },
74 });
75
76 order.fetchData();
77 </script>
78
79 <template>
80 <div class="container">
81 <div
82 v-for="(it, index) in order.data"
83 :key="index"
84 @click="
85 $router.push({
86 path: '/seat/order_detail',
87 query: { orderSn: it.orderSn },
88 })
89 "
90 class="order-item"
91 >
92 <div class="info_box">
93 <img class="cover_img" :src="it.coverImg" />
94 <div class="info">
95 <div class="title">{{ it.name }}</div>
96 <div class="common">时间:{{ it.dateStr }}</div>
97 <div class="common">地址:{{ it.placeName }}</div>
98 <div class="common">订单编号:{{ it.orderSn }}</div>
99 <div class="common">张数:{{ it.ticketNum }}</div>
100 <div class="common">金额:¥{{ it.payAmount }}</div>
101 <div class="status">
102 <div class="label">订单状态:</div>
103 <div class="value">
104 <div
105 :style="{
106 borderColor: status[it.state].color,
107 background: status[it.state].bgColor,
108 color: status[it.state].color,
109 }"
110 class="tag"
111 >
112 {{ status[it.state].txt }}
113 </div>
114 <div v-if="it.state == 0" class="tip">
115 请尽快完成支付,还剩{{ it.min }}{{ it.sec }}
116 </div>
117 </div>
118 </div>
119 </div>
120 </div>
121 <div v-if="it.state == 0" class="btn_box">
122 <div class="pay" @click.stop="order.payment(it)">立即支付</div>
123 <div class="can_pay" @click.stop="order.cancelPayment(it)">
124 取消支付
125 </div>
126 </div>
127 </div>
128
129 <qrCodeDialog
130 :showCodeDialog="order.showCodeDialog"
131 :qrCode="order.qrInfo?.scanCodeUrl"
132 />
133
134 <div class="pagination">
135 <el-pagination
136 v-show="order.total > 0"
137 v-model:current-page="order.pageNo"
138 v-model:page-size="order.pageSize"
139 background
140 layout="prev, pager, next"
141 :total="order.total"
142 @current-change="order.fetchData()"
143 />
144 </div>
145 </div>
146 </template>
147
148 <style scoped lang="scss">
149 .container {
150 width: 1200px;
151 margin: 0 auto;
152 padding: 26px 0;
153 font-family: SourceHanSansCN, SourceHanSansCN;
154 .order-item {
155 display: flex;
156 justify-content: space-between;
157 align-items: center;
158 padding: 36px;
159 background: #fff;
160 box-shadow: 0px 0px 46px 0px rgba(1, 16, 64, 0.08);
161 border-radius: 8px;
162 margin-bottom: 30px;
163 cursor: pointer;
164 .info_box {
165 display: flex;
166 gap: 20px;
167 .cover_img {
168 width: 155px;
169 height: 200px;
170 object-fit: fill;
171 }
172 .info {
173 .title {
174 font-weight: bold;
175 font-size: 22px;
176 color: #000000;
177 line-height: 33px;
178 margin-bottom: 25px;
179 margin-bottom: 10px;
180 }
181 .common {
182 font-weight: 500;
183 font-size: 16px;
184 color: #4e4e4e;
185 margin-bottom: 6px;
186 }
187 .status {
188 display: flex;
189
190 .label {
191 font-weight: 500;
192 font-size: 16px;
193 color: #4e4e4e;
194 line-height: 24px;
195 }
196 .value {
197 display: flex;
198 align-items: center;
199 gap: 20px;
200 .tag {
201 padding: 6px 14px;
202 border-radius: 6px;
203 border: 1px solid #34c759;
204 }
205 .tip {
206 font-size: 16px;
207 color: #f740a6;
208 line-height: 24px;
209 }
210 }
211 }
212 }
213 }
214 .btn_box {
215 display: flex;
216 flex-direction: column;
217 gap: 12px;
218 .pay {
219 width: 175px;
220 height: 40px;
221 background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
222 border-radius: 20px;
223 font-weight: 500;
224 font-size: 16px;
225 color: #ffffff;
226 line-height: 40px;
227 text-align: center;
228 cursor: pointer;
229 }
230 .can_pay {
231 width: 175px;
232 height: 40px;
233 background: #fff;
234 border-radius: 20px;
235 font-weight: 500;
236 font-size: 16px;
237 color: #493ceb;
238 line-height: 40px;
239 border: 1px solid #493ceb;
240 text-align: center;
241 box-sizing: border-box;
242 cursor: pointer;
243 }
244 }
245 }
246 }
247
248 .pagination {
249 display: flex;
250 justify-content: center;
251 }
252 </style>
1 <script setup>
2 import { deleteViewPeople, viewPeopleList } from "./api/index.js";
3 import { ElMessageBox, ElMessage } from "element-plus";
4
5
6 const audience = reactive({
7 data: [],
8 fetchData() {
9 viewPeopleList().then((res) => {
10 audience.data = res.data;
11 });
12 },
13
14 deletePeople(id) {
15 ElMessageBox.confirm("确定删除该观看人吗?", "提示", {
16 confirmButtonText: "确认",
17 cancelButtonText: "取消",
18 type: "warning",
19 draggable: true,
20 })
21 .then(() => {
22 deleteViewPeople({ id }).then(() => {
23 audience.fetchData();
24 ElMessage({
25 type: "success",
26 message: "操作成功",
27 });
28 });
29 })
30 .catch(() => {});
31 },
32 });
33
34 audience.fetchData();
35 </script>
36
37 <template>
38 <div class="container">
39 <div class="title">
40 <div
41 class="add_btn"
42 @click="$router.push({ path: '/seat/add_watch_people' })"
43 >
44 新增
45 </div>
46 观影人管理
47 </div>
48 <div class="content">
49 <div class="people_box">
50 <div
51 v-for="(it, index) in audience.data"
52 :key="index"
53 class="people_item"
54 >
55 <div class="name">{{ it.name }}</div>
56 <div class="idcard">身份证:{{ it.idCard }}</div>
57 <div class="btn" @click="audience.deletePeople(it.id)">删除</div>
58 </div>
59 </div>
60 </div>
61 </div>
62 </template>
63
64 <style scoped lang="scss">
65 div {
66 box-sizing: border-box;
67 }
68 .container {
69 padding: 20px 0;
70 width: 1200px;
71 margin: 0 auto;
72
73 .title {
74 position: relative;
75 padding: 11px;
76 text-align: center;
77 background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
78 font-size: 18px;
79 color: #ffffff;
80 .add_btn {
81 position: absolute;
82 left: 20px;
83 top: 50%;
84 transform: translateY(-50%);
85 width: 68px;
86 height: 24px;
87 border-radius: 12px;
88 border: 1px solid #ffffff;
89 font-weight: 400;
90 font-size: 12px;
91 color: #ffffff;
92 text-align: center;
93 line-height: 24px;
94 box-sizing: border-box;
95 user-select: none;
96 cursor: pointer;
97 }
98 }
99
100 .content {
101 min-height: 590px;
102 background-color: #fff;
103 box-shadow: 0px 0px 46px 0px rgba(1, 16, 64, 0.08);
104 padding: 20px;
105 .people_box {
106 display: flex;
107 flex-wrap: wrap;
108 gap: 20px;
109 .people_item {
110 width: 275px;
111 height: 137px;
112 background: #ffffff;
113 border: 1px solid #dcdfe6;
114 padding: 16px;
115 .name {
116 font-weight: 600;
117 font-size: 16px;
118 color: #2d373f;
119 line-height: 22px;
120 }
121 .idcard {
122 font-size: 16px;
123 color: #95a1a6;
124 line-height: 22px;
125 margin-top: 12px;
126 margin-bottom: 17px;
127 }
128 .btn {
129 width: 69px;
130 height: 32px;
131 background: #e7e6ff;
132 font-weight: 400;
133 font-size: 16px;
134 color: #493ceb;
135 line-height: 32px;
136 text-align: center;
137 cursor: pointer;
138 user-select: none;
139 }
140 }
141 }
142 }
143 }
144 </style>
1 <script setup>
2 import { ElMessage } from "element-plus";
3 import { getPriceLevelInfo, getSiteConfig } from "./api/index.js";
4 const route = useRoute();
5 const router = useRouter();
6
7 const iframeRef = ref();
8
9 // 获取票档
10 const price = reactive({
11 curPriceId: route.query.ticket_block,
12 data: [],
13
14 fetchData() {
15 getPriceLevelInfo({
16 actId: route.query?.actId ?? 1,
17 sessionId: route.query.sessionId,
18 openType: route.query.openType,
19 sitePlace: route.query.sitePlace,
20 ticketType: route.query.ticketType,
21 }).then((res) => {
22 this.data = res.data;
23 // price.curPriceId = route.query.ticket_block
24 });
25 },
26 onClickPrice(e) {
27 // if (selectedSeats.value?.length) {
28 // return ElMessage({ type: "warning", message: "请先取消已选座位" });
29 // }
30 price.curPriceId = e.priceId;
31 },
32 });
33
34 // 座位禁用时图标地址
35 const disabledIconUrl =
36 "http://114.55.227.212:8083/images/20240511/unselect_default.png";
37
38 const siteConfig = reactive({
39 loading: false,
40 data: [],
41 fetchData() {
42 return getSiteConfig({
43 actId: route.query.actId ?? 1,
44 openType: route.query.openType,
45 sessionId: route.query.sessionId,
46 sitePlace: route.query.sitePlace,
47 ticketType: route.query.ticketType,
48 }).then((res) => {
49 const gridSize = 40;
50 const seat_arr = res.data.map((it, index) => {
51 return {
52 ...it,
53 // 这几个是iframe引擎渲染座位必须的属性,规定好的
54 x: gridSize * it.x,
55 y: gridSize * it.y,
56 w: gridSize,
57 h: gridSize,
58 icon: it.state == 1 ? it.selectIcon : disabledIconUrl, // 图片的url
59 active: 0, // 是否选中
60 priceId: route.query.openType == 0 ? it.dayPriceId : it.nightPriceId,
61 };
62 });
63 siteConfig.data = seat_arr;
64 return seat_arr;
65 });
66 },
67 });
68
69 watch(
70 () => price.curPriceId,
71 (priceId) => {
72 siteConfig.data.forEach((it) => {
73 sendMsg("update-seat", {
74 id: it.id,
75 data: {
76 icon:
77 it.state == 1 && priceId == it.priceId
78 ? it.active
79 ? it.unSelectIcon
80 : it.selectIcon
81 : disabledIconUrl,
82 },
83 });
84 });
85 console.log("update完成");
86 },
87 { immediate: true }
88 );
89
90 const sendMsg = (type, data) =>
91 iframeRef.value?.contentWindow.postMessage({ type: type, data: data }, "*");
92
93 /**
94 * 1. 加載iframe 3. 请求API的数据
95 * 2. 等待iframe里面资源加载完毕并触发picker-ready事件
96 * 4. 传递数据给iframe,等待渲染
97 */
98
99 window.addEventListener("message", (e) => {
100 const data = e.data;
101 console.log("[parent]", data);
102
103 if (data.type == "picker-ready") {
104 // apiPromise.then(() => {})
105
106 siteConfig.fetchData().then((res) => {
107 const seat_arr = res.map((it) => {
108 return {
109 ...it,
110 active: 0,
111 icon:
112 it.state == 1 && price.curPriceId == it.priceId
113 ? it.selectIcon
114 : disabledIconUrl,
115 };
116 });
117 // 子页面加载完毕,这里iframeRef一定ok
118 iframeRef.value.contentWindow.postMessage(
119 {
120 type: "load-seats",
121 data: seat_arr,
122 },
123 "*"
124 );
125 });
126 } else if (data.type == "seat-click") {
127 // 子页面点击了座位
128 const seatData = data.data;
129 console.log("座位点击", seatData);
130
131 // 如果座位处于不可点击状态,就return
132 if (seatData.state != 1) return;
133
134 // 如果当前筛选了某种座位,点击的不是这种座位,也返回
135 if (price.curPriceId && seatData.priceId != price.curPriceId) return;
136
137 const newActive = seatData.active == 0 ? 1 : 0;
138 const siteConfigItem = siteConfig.data.find((it) => it.id == seatData.id);
139 if (siteConfigItem) {
140 siteConfigItem.active = newActive;
141 }
142 sendMsg("update-seat", {
143 id: seatData.id,
144 data: {
145 active: newActive,
146 icon: newActive ? seatData.unSelectIcon : seatData.selectIcon,
147 },
148 });
149 }
150 });
151 const deleteSiteConfigItem = (seatData) => {
152 const newActive = seatData.active == 0 ? 1 : 0;
153 const siteConfigItem = siteConfig.data.find((it) => it.id == seatData.id);
154 if (siteConfigItem) {
155 siteConfigItem.active = newActive;
156 }
157 sendMsg("update-seat", {
158 id: seatData.id,
159 data: { icon: newActive ? seatData.unSelectIcon : seatData.selectIcon },
160 });
161 };
162
163 /** 所选座位 */
164 const selectedSeats =
165 computed(() => siteConfig.data.filter((it) => it.active == 1)) ?? [];
166
167 /** 所选座位价格 */
168 const sumPrice = computed(() => {
169 return selectedSeats.value.reduce((total, item) => {
170 const price =
171 route.query.openType == 0
172 ? Number(item.dayPrice)
173 : Number(item.nightPrice);
174 return total + price;
175 }, 0);
176 });
177
178 const toConfirmOrder = () => {
179 const seatIds = selectedSeats.value.map((it) => it.id);
180 if (!seatIds.length)
181 return ElMessage({ type: "warning", message: "请先选择座位" });
182
183 router.push({
184 path: "/seat/confirm_order",
185 query: {
186 openType: route.query.openType,
187 sessionId: route.query.sessionId,
188 sitePlace: route.query.sitePlace,
189 ticketType: route.query.ticketType,
190 seatIds: seatIds.join(","),
191 },
192 });
193 };
194
195 price.fetchData();
196 </script>
197
198 <template>
199 <div class="container">
200 <div class="top">
201 <div class="time">
202 <span>{{ route.query?.time_txt }}</span>
203 <span class="place">{{ route.query.sitePlace }}</span>
204 </div>
205 <div class="price_tab">
206 <div
207 v-for="(it, index) in price.data"
208 class="tab_item"
209 :class="{ tabActive: it.priceId == price.curPriceId }"
210 @click="price.onClickPrice(it)"
211 >
212 <img class="seat" :src="it.selectIcon" />
213 <span class="price">{{ it.price }}¥</span>
214 </div>
215 </div>
216 </div>
217
218 <div v-if="selectedSeats?.length" class="bottom">
219 <div class="seat_box">
220 <!-- v-for="(it, index) in selectedSeats" -->
221 <div v-for="(it, index) in selectedSeats" class="seat_item">
222 <img class="seat_icon" :src="it.selectIcon" />
223 <span class="num">{{ it.area }}{{ it.pai }}{{ it.no }}</span>
224 <el-icon
225 style="cursor: pointer"
226 color="#ccc"
227 @click="deleteSiteConfigItem(it)"
228 ><CircleCloseFilled
229 /></el-icon>
230 </div>
231 </div>
232 <div class="pay">
233 <div class="sum">¥{{ sumPrice?.toFixed(2) }}</div>
234 <div class="pay_btn" @click="toConfirmOrder()">立即购买</div>
235 </div>
236 </div>
237
238 <div class="iframeBox">
239 <iframe
240 ref="iframeRef"
241 class="iframe"
242 id="iframe"
243 src="http://seat-choose.parent4relax.com/#/seat-picker"
244 ></iframe>
245 </div>
246 </div>
247 </template>
248
249 <style scoped lang="scss">
250 .container {
251 width: 1200px;
252 margin: 0 auto;
253 padding: 20px;
254
255 .top {
256 width: 100%;
257 background-color: #fff;
258 padding: 20px;
259 margin-bottom: 10px;
260 border-radius: 6px;
261 .time {
262 font-size: 18px;
263 font-weight: 600;
264 margin-bottom: 10px;
265 .place {
266 color: #7e8489;
267 margin-left: 15px;
268 }
269 }
270
271 .price_tab {
272 display: flex;
273 align-items: center;
274 flex-wrap: wrap;
275 gap: 10px;
276 .tabActive {
277 background: #eeeeee !important;
278 border: 2px solid #7e8489 !important;
279 }
280 .tab_item {
281 display: flex;
282 align-items: center;
283 padding: 10px 14px;
284 background: #f5f7f8;
285 border-radius: 30px;
286 border: 2px solid #dcdedf;
287 font-size: 16px;
288 color: #646666;
289 cursor: pointer;
290 user-select: none;
291
292 .seat {
293 width: 14px;
294 height: 14px;
295 margin-right: 5px;
296 }
297 }
298 }
299 }
300
301 .iframeBox {
302 border-radius: 6px;
303 background-color: #fff;
304 padding: 20px;
305 margin-bottom: 20px;
306 }
307
308 .iframe {
309 width: 100%;
310 height: 500px;
311 border: none;
312 background-color: #f7f8fa;
313 }
314
315 .bottom {
316 border-radius: 6px;
317 background-color: #fff;
318 padding: 20px;
319 margin-bottom: 20px;
320 .seat_box {
321 display: flex;
322 flex-wrap: wrap;
323 gap: 10px;
324 width: 100%;
325 .seat_item {
326 display: flex;
327 align-items: center;
328 padding: 10px 14px;
329 font-size: 16px;
330 color: #29343c;
331 background: #eeeeee;
332 border-radius: 30px;
333 border: 2px solid #7e8489;
334 user-select: none;
335 .seat_icon {
336 width: 14px;
337 height: 14px;
338 margin-right: 5px;
339 }
340 .num {
341 margin-right: 5px;
342 }
343 }
344 }
345
346 .pay {
347 display: flex;
348 justify-content: space-between;
349 align-items: center;
350 margin-top: 10px;
351 .sum {
352 font-weight: 600;
353 font-size: 22px;
354 color: #493ceb;
355 }
356 .pay_btn {
357 width: 200px;
358 height: 40px;
359 background: #493ceb;
360 border-radius: 20px;
361 margin-top: 10px;
362 font-size: 14px;
363 color: #fff;
364 line-height: 40px;
365 text-align: center;
366 cursor: pointer;
367 font-weight: 600;
368 user-select: none;
369 }
370 }
371 }
372 }
373 </style>
1 <script setup></script>
2
3 <template>
4 <div class="view">
5 <router-view />
6 </div>
7 </template>
8
9 <style scoped lang="scss">
10 .view {
11 min-width: 1024px;
12 max-width: 1920px;
13 margin: 0 auto;
14 }
15 </style>
1 <script setup>
2 import dayjs from "dayjs";
3 import useUserStore from "@/store/modules/user";
4 import { setToken, getToken } from "./utils/local-store.js";
5 import { md5 } from "md5js";
6 import { ElMessageBox, ElMessage } from "element-plus";
7 import {
8 loginFree,
9 activityDetail,
10 sessionDetail,
11 getSitePlaceInfo,
12 getPriceLevelInfo,
13 } from "./api/index.js";
14
15 const route = useRoute();
16 const router = useRouter();
17 const userStore = useUserStore();
18
19 const props = defineProps({
20 activityId: [String, Number],
21 });
22
23 // 用户免登录
24 const login = async (userId) => {
25 const sign = md5(`uid=${userId}lgo1acfkw51jfo`);
26 return loginFree({
27 userId: userId,
28 sign,
29 }).then((res) => {
30 setToken(res.data.token);
31 resolve(res.data);
32 console.log(33333, res);
33 });
34 };
35
36 const select_form = reactive({
37 venueItem: {
38 id: 0,
39 dateStr: "",
40 dayOpen: 1,
41 nightOpen: 1,
42 type: 0,
43 }, // 所选场次
44 session: -1, // 日/夜场 0:日场 1:夜场
45 place: "", // 场馆
46 ticket_block: 0, // 票档
47 onClickVenue(e, index) {
48 if (e.state == 1) return; // 表示
49 select_form.venueItem = e;
50 if (
51 (e.dayOpen == 0 && select_form.session == 0) ||
52 (e.nightOpen == 0 && select_form.session == 1)
53 ) {
54 select_form.session = -1;
55 select_form.place = "";
56 select_form.ticket_block = 0;
57 }
58 },
59 // 选择日/夜场
60 onClickSession(e) {
61 if (
62 (e == 0 && select_form.venueItem?.dayOpen == 1) ||
63 (e == 1 && select_form.venueItem?.nightOpen == 1)
64 ) {
65 select_form.session = e;
66 // select_form.place = "";
67 select_form.ticket_block = 0;
68 }
69 },
70 // 选择场馆
71 onClickPlace(e) {
72 if (e.state == 1) return;
73 select_form.place = e.placeName;
74 select_form.ticket_block = 0;
75 },
76 // 选择票档
77 onClickPrice(e) {
78 if (e.state == 1) return;
79 select_form.ticket_block = e.priceId;
80 },
81 // 去选座
82 toSelectSeat() {
83 if (!select_form.venueItem?.id)
84 return ElMessage({ type: "warning", message: "请选择时间" });
85 if (select_form.session == -1)
86 return ElMessage({ type: "warning", message: "请选择场次" });
87 if (!select_form.place)
88 return ElMessage({ type: "warning", message: "请选择场馆" });
89 if (!select_form.ticket_block)
90 return ElMessage({ type: "warning", message: "请选择票档" });
91
92 router.push({
93 path: "/seat/seat_picker",
94 query: {
95 openType: select_form.session,
96 sessionId: select_form.venueItem?.id,
97 sitePlace: select_form.place,
98 ticketType: select_form.venueItem?.type,
99 ticket_block: select_form.ticket_block,
100 time_txt: select_form.venueItem?.dateStr,
101 },
102 });
103 },
104 });
105
106 // 活动详情
107 const detail = reactive({
108 loading: false,
109 data: null,
110 fetchData() {
111 this.loading = true;
112 activityDetail({ actId: props.activityId })
113 .then((res) => {
114 this.data = res.data;
115 })
116 .finally(() => (this.loding = false));
117 },
118 });
119
120 // 获取场次信息
121 const timeVenue = reactive({
122 loading: false,
123 data: [],
124 fetchData() {
125 this.loading = true;
126 sessionDetail({ actId: props.activityId })
127 .then((res) => {
128 this.data = res.data;
129 })
130 .finally(() => (this.loading = false));
131 },
132 });
133
134 // 获取场馆
135 const sitePlaceInfo = reactive({
136 data: [
137 { placeName: "B4", state: "0" },
138 { placeName: "B6", state: "0" },
139 ],
140 fetchData() {
141 console.log(select_form.venueItem?.id, select_form.session);
142 getSitePlaceInfo({
143 sessionId: select_form.venueItem?.id,
144 openType: select_form.session,
145 }).then((res) => {
146 this.data = res.data;
147 });
148 },
149 });
150
151 // 获取票档
152 const price = reactive({
153 data: [],
154 fetchData() {
155 getPriceLevelInfo({
156 actId: props.activityId,
157 sessionId: select_form.venueItem?.id,
158 openType: select_form.session,
159 sitePlace: select_form.place,
160 ticketType: select_form.venueItem?.type,
161 }).then((res) => {
162 this.data = res.data;
163 });
164 },
165 });
166
167 watchEffect(() => {
168 if (select_form.session != -1 && select_form.venueItem?.id) {
169 if (select_form.session == 1 && select_form.place == "B4") {
170 select_form.place = "";
171 }
172 sitePlaceInfo.fetchData();
173 }
174 });
175
176 watchEffect(() => {
177 if (
178 select_form.venueItem?.id &&
179 select_form.session != -1 &&
180 select_form.place
181 ) {
182 price.fetchData();
183 }
184 });
185
186 // 主流程开始
187 watch(
188 () => props.activityId,
189 async (activityId) => {
190 if (!activityId) {
191 // [TODO] dialog提示缺少活動ID讓然後返回
192 ElMessageBox.confirm("缺少活动id", "提示", {
193 confirmButtonText: "确认",
194 type: "warning",
195 draggable: true,
196 }).then((res) => {
197 router.push("/");
198 });
199 return;
200 }
201
202 // 检查登录
203 const ticketUserToken = getToken();
204 if (!ticketUserToken) {
205 const userId = 1; // [TODO] 从原项目中取已登录的用户ID
206 if (!userId) {
207 // 未登录,跳转登录 [TODO]
208 return;
209 }
210 await login(userId);
211 }
212
213 detail.fetchData();
214 timeVenue.fetchData();
215 },
216 { immediate: true }
217 );
218 </script>
219
220 <template>
221 <div>
222 <!-- top -->
223 <div class="container top">
224 <img class="cover_img" :src="detail.data?.coverImg" />
225 <div class="info">
226 <div class="title">{{ detail.data?.name }}</div>
227 <div class="time">
228 时间:{{
229 detail.data?.startTime
230 ? dayjs(detail.data?.startTime).format("YYYY.MM.DD")
231 : ""
232 }}
233 {{ detail.data?.startTime ? dayjs().format("ddd") : "" }}
234 {{
235 detail.data?.endTime
236 ? dayjs(detail.data?.endTime).format("YYYY.MM.DD")
237 : ""
238 }}
239 {{
240 detail.data?.endTime
241 ? dayjs(detail.data?.endTime).format("ddd")
242 : ""
243 }}
244 </div>
245 <div class="address">地址:{{ detail.data?.address }}</div>
246 <!-- 时间 -->
247 <div class="select_item_box">
248 <div class="label">时间</div>
249 <div class="select_item">
250 <div
251 v-for="(it, index) in timeVenue.data"
252 :key="index"
253 :class="[
254 it.id == select_form.venueItem?.id ? 'tagActive' : 'tag',
255 ]"
256 @click="select_form.onClickVenue(it)"
257 >
258 {{ it.dateStr }}
259 <div v-if="it.type == 1" class="tag_t">套票</div>
260 </div>
261 </div>
262 </div>
263 <!-- 场次 -->
264 <div class="select_item_box">
265 <div class="label">场次</div>
266 <div class="select_item">
267 <div
268 :class="[
269 select_form.venueItem?.dayOpen == 1
270 ? select_form.session == 0
271 ? 'tagActive'
272 : 'tag'
273 : 'tagDisabled',
274 ]"
275 @click="select_form.onClickSession(0)"
276 >
277 日场
278 </div>
279 <div
280 :class="[
281 select_form.venueItem?.nightOpen == 1
282 ? select_form.session == 1
283 ? 'tagActive'
284 : 'tag'
285 : 'tagDisabled',
286 ]"
287 @click="select_form.onClickSession(1)"
288 >
289 夜场
290 </div>
291 </div>
292 </div>
293 <!-- 场馆 -->
294 <div class="select_item_box">
295 <div class="label">场馆</div>
296 <div class="select_item">
297 <div
298 v-for="(it, index) in sitePlaceInfo.data"
299 :key="index"
300 :class="[
301 it.state == 0
302 ? it.placeName == select_form.place
303 ? 'tagActive'
304 : 'tag'
305 : 'tagDisabled',
306 ]"
307 @click="select_form.onClickPlace(it)"
308 >
309 {{ it.placeName }}
310 </div>
311 </div>
312 </div>
313 <!-- 票档 -->
314 <div
315 v-if="price.data?.length && select_form.place"
316 class="select_item_box"
317 >
318 <div class="label">票档</div>
319 <div class="select_item">
320 <div
321 v-for="(it, index) in price.data"
322 :key="index"
323 :class="[
324 it.state == 0
325 ? it.priceId == select_form.ticket_block
326 ? 'tagActive'
327 : 'tag'
328 : 'tagDisabled',
329 ]"
330 @click="select_form.onClickPrice(it)"
331 >
332 {{ it.price }}
333 </div>
334 </div>
335 </div>
336 <!-- button -->
337 <div class="btn" @click="select_form.toSelectSeat()">选座购票</div>
338 </div>
339 </div>
340
341 <!-- bottom -->
342 <div class="container bottom">
343 <div class="title">活动介绍</div>
344 <div class="rich_content" v-html="detail.data?.introduceInfo"></div>
345
346 <div class="title" style="margin-top: 30px">购票须知</div>
347 <div class="rich_content" v-html="detail.data?.buyNotice"></div>
348 </div>
349 </div>
350 </template>
351
352 <style scoped lang="scss">
353 .container {
354 width: 1200px;
355 margin: 0 auto;
356 background-color: #fff;
357 box-shadow: 0px 0px 46px 0px rgba(1, 16, 64, 0.08);
358 border-radius: 8px;
359 box-sizing: border-box;
360 font-family: SourceHanSansCN, SourceHanSansCN;
361 padding-bottom: 20px;
362 }
363
364 .top {
365 display: flex;
366 padding: 19px;
367 margin-top: 26px;
368 .cover_img {
369 width: 390px;
370 height: 517px;
371 object-fit: fill;
372 margin-right: 36px;
373 }
374
375 .info {
376 padding-top: 12px;
377 .title {
378 font-weight: bold;
379 font-size: 28px;
380 color: #000000;
381 line-height: 42px;
382 margin-bottom: 34px;
383 }
384 .time {
385 font-weight: 500;
386 font-size: 16px;
387 color: #4a4a4a;
388 line-height: 24px;
389 margin-bottom: 16px;
390 }
391 .address {
392 font-weight: 500;
393 font-size: 16px;
394 color: #4a4a4a;
395 line-height: 24px;
396 margin-bottom: 33px;
397 }
398
399 .select_item_box {
400 display: flex;
401 margin-bottom: 30px;
402 &:last-child {
403 margin-bottom: 0;
404 }
405 .label {
406 font-weight: 600;
407 font-size: 16px;
408 color: #000;
409 line-height: 24px;
410 margin-right: 12px;
411 flex-shrink: 0;
412 }
413
414 .select_item {
415 display: flex;
416 flex-wrap: wrap;
417 gap: 10px;
418 user-select: none;
419 .tag_t {
420 padding: 1px 15px;
421 font-weight: 400;
422 font-size: 14px;
423 color: #493ceb;
424 border-radius: 6px;
425 border: 1px solid #453dea;
426 margin-left: 5px;
427 }
428 .tag {
429 display: flex;
430 padding: 12px 18px;
431 background: #eeeeee;
432 border-radius: 4px;
433 border: 1px solid #29343c;
434 font-size: 14px;
435 color: #4a4a4a;
436 cursor: pointer;
437 }
438
439 .tagActive {
440 display: flex;
441 padding: 12px 18px;
442 background: #fff;
443 border-radius: 4px;
444 border: 1px solid #493ceb;
445 font-size: 14px;
446 color: #493ceb;
447 cursor: pointer;
448 }
449 .tagDisabled {
450 padding: 12px 18px;
451 background: #878787;
452 border-radius: 4px;
453 border: 1px solid #29343c;
454 font-size: 14px;
455 color: #4a4a4a;
456 cursor: no-drop;
457 }
458 }
459 }
460 .btn {
461 width: 175px;
462 height: 40px;
463 background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
464 border-radius: 20px;
465 line-height: 40px;
466 text-align: center;
467 font-weight: 500;
468 font-size: 16px;
469 color: #ffffff;
470 cursor: pointer;
471 }
472 }
473 }
474
475 .bottom {
476 padding: 50px;
477 margin-top: 30px;
478 .title {
479 padding: 20px 30px;
480 background: linear-gradient(270deg, #493ceb 0%, #8623fc 100%);
481 font-weight: bold;
482 font-size: 20px;
483 color: #ffffff;
484 line-height: 30px;
485 margin-bottom: 30px;
486 }
487 .rich_content {
488 margin-top: 30px;
489 }
490 }
491 </style>
1 /** 用户登录token储存的key */
2 export const TOKEN_KEY = "SEAT_TOKEN";
3
4 /** 设置token */
5 export const setToken = (token) => localStorage.setItem(TOKEN_KEY, token);
6
7 /**
8 * 获取登录token
9 * @param drop 是否清空
10 */
11 export const getToken = (drop = false) => {
12 let token = localStorage.getItem(TOKEN_KEY);
13 if (!token) return null;
14 if (drop) localStorage.removeItem(TOKEN_KEY);
15 return token;
16 };
1 // http.js
2
3 import axios from "axios";
4 import { getToken } from "./local-store";
5 import { ElMessage } from "element-plus";
6
7 const baseURL = "http://101.43.15.205:8084"; //"http://book.xiaojinyu.games"; // 这里填入你的基础 API URL
8 const timeout = 15000; // 请求超时时间
9
10 const http = axios.create({
11 baseURL,
12 timeout,
13 headers: {
14 "Content-Type": "application/json",
15 },
16 });
17
18 // 请求拦截器
19 http.interceptors.request.use(
20 (config) => {
21 // 在发送请求之前做些什么
22 const TOKEN = getToken();
23 config.headers.Authorization = TOKEN;
24 if (config.method == "get") config.params = config.data;
25 return config;
26 },
27 (error) => {
28 return Promise.reject(error);
29 }
30 );
31
32 // 响应拦截器
33 http.interceptors.response.use(
34 (response) => {
35 // 判断是否有异常
36 let error = null; // 若无异常此值为null
37 if (response.status !== 200) {
38 error = Error(`Request failed with statuCode ${response.status}`);
39 }
40
41 if (response.data.code != 200) {
42 return ElMessage({ type: "error", message: response.data.msg });
43 }
44
45 return response.data;
46 },
47 (error) => {
48 // 对响应错误做点什么
49 return Promise.reject(error);
50 }
51 );
52
53 // 封装请求函数
54 const request = (method, url, data = null) => {
55 return http({
56 method,
57 url,
58 data,
59 });
60 };
61
62 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!