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 { 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 /** 用户登录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!