seat-picker.vue 9.12 KB
<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>