|
@@ -1,26 +1,4682 @@
|
|
|
<script setup>
|
|
|
-import Chat from "../components/Chat.vue";
|
|
|
+import { ref, reactive, onMounted, watch, nextTick } from "vue";
|
|
|
+import { useRoute } from "vue-router";
|
|
|
+// VR
|
|
|
+import "aframe";
|
|
|
+// i18n
|
|
|
+import { useI18n } from "vue-i18n";
|
|
|
+// Axios
|
|
|
+import axios from "axios";
|
|
|
+// Moment
|
|
|
+import moment from "moment";
|
|
|
+// Animate
|
|
|
+import "animate.css";
|
|
|
+// Swiper
|
|
|
+import { Swiper, SwiperSlide } from "swiper/vue";
|
|
|
+import { Navigation, Autoplay } from "swiper/modules";
|
|
|
+import "swiper/css";
|
|
|
+import "swiper/css/navigation";
|
|
|
+// Recorder
|
|
|
+import Recorder from "recorder-core";
|
|
|
+import "recorder-core/src/engine/mp3";
|
|
|
+import "recorder-core/src/engine/mp3-engine";
|
|
|
+// QR Code
|
|
|
+import { QrcodeStream, QrcodeDropZone, QrcodeCapture } from "vue-qrcode-reader";
|
|
|
+// Components
|
|
|
import Navbar from "../components/Navbar.vue";
|
|
|
+import TicketPurchase from "../components/TicketPurchase.vue";
|
|
|
+
|
|
|
+const { t, locale } = useI18n();
|
|
|
+
|
|
|
+const route = useRoute();
|
|
|
+const routeParam = ref(null);
|
|
|
+
|
|
|
+let qrCodeDialog = ref(false);
|
|
|
+let qrCodeLoading = ref(true);
|
|
|
+let location = ref(""); // 當前位置
|
|
|
+
|
|
|
+// QR Code 掃描
|
|
|
+function onDetect(detectedCodes) {
|
|
|
+ console.log("detectedCodes", detectedCodes);
|
|
|
+
|
|
|
+ const url = new URL(detectedCodes[0].rawValue);
|
|
|
+ location.value = url.searchParams.get("location");
|
|
|
+
|
|
|
+ let val;
|
|
|
+ let lang = getLang();
|
|
|
+
|
|
|
+ if (lang === "en") {
|
|
|
+ switch (location.value) {
|
|
|
+ case "B1 鼎泰豐":
|
|
|
+ val = "b1 Din Tai Fung";
|
|
|
+ break;
|
|
|
+ case "B1 中環":
|
|
|
+ val = "b1 Centre";
|
|
|
+ break;
|
|
|
+ case "B1 2號電梯":
|
|
|
+ val = "b1 Elevator No.2";
|
|
|
+ break;
|
|
|
+ case "B1 4號電梯":
|
|
|
+ val = "b1 Elevator No.4";
|
|
|
+ break;
|
|
|
+ case "B1 3號電梯":
|
|
|
+ val = "b1 Elevator No.3";
|
|
|
+ break;
|
|
|
+ case "1F 信義環":
|
|
|
+ val = "1f South Xinyi";
|
|
|
+ break;
|
|
|
+ case "1F 3號電梯":
|
|
|
+ val = "1f Elevator No.3";
|
|
|
+ break;
|
|
|
+ case "2F 3號電梯":
|
|
|
+ val = "2f Elevator No.3";
|
|
|
+ break;
|
|
|
+ case "88F 觀景台":
|
|
|
+ val = "88f Observatory";
|
|
|
+ break;
|
|
|
+ case "89F 觀景台":
|
|
|
+ val = "89f Observatory";
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else if (lang === "ch") {
|
|
|
+ val = location.value;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (location.value && location.value !== "") {
|
|
|
+ qrCodeDialog.value = false;
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: `${t("current_location")}:${val}`,
|
|
|
+ });
|
|
|
+
|
|
|
+ changeLocation(location.value); // 選擇導覽位置
|
|
|
+ } else {
|
|
|
+ qrCodeDialog.value = false;
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: "讀取錯誤,請重新掃描。",
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 判斷 vue-qrcode-reader 是否加載完成
|
|
|
+function onInit(capabilities) {
|
|
|
+ qrCodeLoading.value = false;
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ // 取得當前路由參數
|
|
|
+ routeParam.value = route.path.replace("/", "");
|
|
|
+ console.log("網址參數", routeParam.value);
|
|
|
+ window.addEventListener("resize", handleResize);
|
|
|
+});
|
|
|
+
|
|
|
+const userMessage = ref("");
|
|
|
+let modules = [Navigation, Autoplay]; // Swiper
|
|
|
+
|
|
|
+// AI 客服回覆訊息
|
|
|
+let messages = ref([]);
|
|
|
+
|
|
|
+watch(messages.value, (val) => {
|
|
|
+ console.log("messages", val);
|
|
|
+ scrollToBottom();
|
|
|
+ updateMenuHeight();
|
|
|
+});
|
|
|
+
|
|
|
+let ad = ref({}); // 彈跳視窗廣告
|
|
|
+let qaQuery = reactive([]);
|
|
|
+
|
|
|
+const chatArea = ref(null); // 對話框
|
|
|
+
|
|
|
+// 滾動到對話框底部
|
|
|
+const scrollToBottom = () => {
|
|
|
+ setTimeout(() => {
|
|
|
+ // chatArea.value.scrollTop = chatArea.value.scrollHeight;
|
|
|
+
|
|
|
+ window.scrollTo({
|
|
|
+ top: document.body.scrollHeight,
|
|
|
+ behavior: "smooth", // 平滑滾動
|
|
|
+ });
|
|
|
+ }, 100);
|
|
|
+
|
|
|
+ // let list = messages.value;
|
|
|
+ // const item = list[list.length - 1];
|
|
|
+ // if (item.label === "text") {
|
|
|
+ // setTimeout(() => {
|
|
|
+ // chatArea.value.scrollTop = chatArea.value.scrollHeight;
|
|
|
+ // }, 100);
|
|
|
+ // }
|
|
|
+};
|
|
|
+
|
|
|
+// 對話選項(按鈕)
|
|
|
+function setBtnValue(val) {
|
|
|
+ console.log("value", val);
|
|
|
+ userMessage.value = val;
|
|
|
+ sendMessage("text");
|
|
|
+}
|
|
|
+
|
|
|
+// 傳送訊息 (如 type="text" 代表為純文字訊息,不需語音回覆)
|
|
|
+async function sendMessage(type = "") {
|
|
|
+ if (userMessage.value === "") {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("sendMessage", userMessage.value);
|
|
|
+ qaQuery.push(userMessage.value);
|
|
|
+
|
|
|
+ let url = "https://devbox10.itri-nlp.tw:38125/v1/qa/";
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 使用者訊息
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ body: userMessage.value,
|
|
|
+ author: "user",
|
|
|
+ });
|
|
|
+
|
|
|
+ userMessage.value = "";
|
|
|
+ // hideMenu.value = true;
|
|
|
+
|
|
|
+ axios
|
|
|
+ .post(
|
|
|
+ url,
|
|
|
+ new URLSearchParams({
|
|
|
+ query: JSON.stringify(qaQuery), // 使用者問句
|
|
|
+ language: selectLang.value, // 語系
|
|
|
+ category: assignCategory.value, // 類別
|
|
|
+ subtype: assignSubtype.value, // 詳細類別(非必填)
|
|
|
+ from_loc: "", // 定位點
|
|
|
+ to_loc: "", // 導覽點
|
|
|
+ }),
|
|
|
+ {
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/x-www-form-urlencoded",
|
|
|
+ },
|
|
|
+ }
|
|
|
+ )
|
|
|
+ .then((response) => {
|
|
|
+ // if (showAnchor.value) {
|
|
|
+ // getVideo(response.data);
|
|
|
+ // }
|
|
|
+
|
|
|
+ // AI 客服回傳訊息
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: response.data.response,
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log("response", response);
|
|
|
+
|
|
|
+ if (type !== "text") {
|
|
|
+ handleTTS(response.data.response); // 取得語音回覆
|
|
|
+ }
|
|
|
+
|
|
|
+ if (response.data.data.length) {
|
|
|
+ let info = {
|
|
|
+ buttonList: [], // 按鈕
|
|
|
+ ticketList: [], // 票券
|
|
|
+ };
|
|
|
+ let labelContent;
|
|
|
+
|
|
|
+ response.data.data.map((item) => {
|
|
|
+ if (item.type === "button") {
|
|
|
+ console.log("按鈕");
|
|
|
+ info.buttonList.push(item);
|
|
|
+ labelContent = "ticket";
|
|
|
+ } else if (item.type === "ticket") {
|
|
|
+ console.log("票");
|
|
|
+ info.ticketList.push(item);
|
|
|
+ labelContent = "ticket";
|
|
|
+ } else if (item.type === "brand") {
|
|
|
+ console.log("品牌");
|
|
|
+ info.ticketList.push(item);
|
|
|
+ labelContent = "brand";
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log("info", info);
|
|
|
+ console.log("labelContent", labelContent);
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ messages.value.push({
|
|
|
+ label: labelContent,
|
|
|
+ author: "ai",
|
|
|
+ body: info,
|
|
|
+ });
|
|
|
+ }, 10);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error("Error:", error);
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log("messages.value", messages.value);
|
|
|
+ } catch (error) {
|
|
|
+ console.log("error", error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// let isTTSVideo = ref(false); // API 是否回傳影片
|
|
|
+// let ttsVideo = ref(null); // 影片 dom
|
|
|
+// let ttsVideoSrc = ref(""); // 影片路徑
|
|
|
+// let videoPause = ref(false); // 暫停按鈕
|
|
|
+let hideMenu = ref(false); // 底部選單
|
|
|
+let showInput = ref(false); // 輸入框
|
|
|
+
|
|
|
+// 取得 mp4
|
|
|
+// async function getVideo(data) {
|
|
|
+// console.log("getVideo data", data);
|
|
|
+// // let url = `https://cmm.ai:9101/tts?message=${data.response}&type=101`;
|
|
|
+// let url = `https://bf18-61-230-0-215.ngrok-free.app/tts?message=${data.response}&type=101`;
|
|
|
+
|
|
|
+// try {
|
|
|
+// const response = await axios.post(url);
|
|
|
+// console.log("response", response);
|
|
|
+
|
|
|
+// if (response.status === 200) {
|
|
|
+// video.value.pause();
|
|
|
+// videoPause.value = false;
|
|
|
+
|
|
|
+// // ttsVideoSrc.value = `https://cmm.ai:9101/${response.data.url}`;
|
|
|
+// // ttsVideoSrc.value = `https://bf18-61-230-0-215.ngrok-free.app/${response.data.url}`;
|
|
|
+// ttsVideoSrc.value = response.data.url;
|
|
|
+// await ttsVideo.value.load();
|
|
|
+// await ttsVideo.value.play();
|
|
|
+
|
|
|
+// // AI 客服回傳訊息
|
|
|
+// // messages.value.splice(-1, 1);
|
|
|
+
|
|
|
+// messages.value.push({
|
|
|
+// type: data.type,
|
|
|
+// body: data.response,
|
|
|
+// author: data.author,
|
|
|
+// });
|
|
|
+
|
|
|
+// // isTTSVideo.value = true;
|
|
|
+
|
|
|
+// // 判斷影片是否結束
|
|
|
+// const videoPromise = new Promise((resolve, reject) => {
|
|
|
+// ttsVideo.value.onended = () => resolve(true);
|
|
|
+// });
|
|
|
+
|
|
|
+// const videoCompleted = await videoPromise; // 影片結束
|
|
|
+
|
|
|
+// if (videoCompleted) {
|
|
|
+// console.log("影片結束");
|
|
|
+// // isTTSVideo.value = false; // 切換為初始影片
|
|
|
+// hideAnchorPrologue.value = false; // 切換開場白影片
|
|
|
+// }
|
|
|
+
|
|
|
+// console.log("videoCompleted", videoCompleted);
|
|
|
+// }
|
|
|
+// } catch (error) {
|
|
|
+// console.log("error", error);
|
|
|
+// }
|
|
|
+// }
|
|
|
+
|
|
|
+// 影片暫停/播放
|
|
|
+// function handleVideo(isTTS) {
|
|
|
+// if (videoPause.value) {
|
|
|
+// if (isTTS) {
|
|
|
+// ttsVideo.value.play();
|
|
|
+// } else {
|
|
|
+// video.value.play();
|
|
|
+// }
|
|
|
+// videoPause.value = false;
|
|
|
+// } else {
|
|
|
+// if (isTTS) {
|
|
|
+// ttsVideo.value.pause();
|
|
|
+// } else {
|
|
|
+// video.value.pause();
|
|
|
+// }
|
|
|
+// videoPause.value = true;
|
|
|
+// }
|
|
|
+// }
|
|
|
+
|
|
|
+function getLang() {
|
|
|
+ let lang = localStorage.getItem("lang");
|
|
|
+ let langVal = "";
|
|
|
+
|
|
|
+ switch (lang) {
|
|
|
+ case "zh-tw":
|
|
|
+ langVal = "ch";
|
|
|
+ break;
|
|
|
+ case "en-us":
|
|
|
+ langVal = "en";
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return langVal;
|
|
|
+}
|
|
|
+
|
|
|
+let showAd = ref(false);
|
|
|
+
|
|
|
+// 取得廣告
|
|
|
+async function setAd() {
|
|
|
+ let lang = getLang();
|
|
|
+ console.log("lang", lang);
|
|
|
+ let url = `https://cmm.ai:9101/ad?language=${lang}`;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await axios.get(url);
|
|
|
+ console.log("Ad response", response);
|
|
|
+
|
|
|
+ ad.value = response.data.data.body;
|
|
|
+ console.log("Ad", ad.value);
|
|
|
+ } catch (error) {
|
|
|
+ console.log("error", error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+let video = ref(null);
|
|
|
+
|
|
|
+function videoPlay() {
|
|
|
+ video.value.load();
|
|
|
+ video.value.play();
|
|
|
+}
|
|
|
+
|
|
|
+// 底部選單
|
|
|
+const menu = ref(null);
|
|
|
+const menuHeight = ref(0);
|
|
|
+
|
|
|
+// let isRotate = ref(false);
|
|
|
+let isLanguagePage = ref(true);
|
|
|
+let selectLang = ref("");
|
|
|
+let chatLoading = ref(true);
|
|
|
+
|
|
|
+watch(isLanguagePage, (val) => {
|
|
|
+ if (!val) {
|
|
|
+ setTimeout(updateMenuHeight, 500);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const handleResize = () => {
|
|
|
+ updateMenuHeight();
|
|
|
+};
|
|
|
+
|
|
|
+// 取得底部選單高度
|
|
|
+const updateMenuHeight = () => {
|
|
|
+ nextTick(() => {
|
|
|
+ if (menu.value) {
|
|
|
+ menuHeight.value = menu.value.clientHeight;
|
|
|
+ chatLoading.value = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+function chooseLang(lang) {
|
|
|
+ console.log("選擇語言:", lang);
|
|
|
+ selectLang.value = lang;
|
|
|
+ isLanguagePage.value = false;
|
|
|
+ locale.value = lang; // i18n locale
|
|
|
+ localStorage.setItem("lang", lang);
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("prologue"),
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("service"),
|
|
|
+ });
|
|
|
+
|
|
|
+ // messages.value.push({
|
|
|
+ // label: "text",
|
|
|
+ // author: "ai",
|
|
|
+ // body: "請選擇問題類型:",
|
|
|
+ // });
|
|
|
+
|
|
|
+ // messages.value.push({
|
|
|
+ // label: "btn-list",
|
|
|
+ // author: "ai",
|
|
|
+ // body: "",
|
|
|
+ // });
|
|
|
+
|
|
|
+ setAd();
|
|
|
+ handleClick();
|
|
|
+
|
|
|
+ console.log(" messages.value", messages.value);
|
|
|
+ setTimeout(() => {
|
|
|
+ showAd.value = true;
|
|
|
+ // videoPlay();
|
|
|
+ }, 500);
|
|
|
+}
|
|
|
+
|
|
|
+let assignSubtype = ref("");
|
|
|
+let assignCategory = ref("");
|
|
|
+let assignCategoryIndex = ref(null);
|
|
|
+
|
|
|
+// 美食伴手禮
|
|
|
+let diningList = reactive([
|
|
|
+ {
|
|
|
+ value: "特色/高空餐廳",
|
|
|
+ text: "food_souvenirs_info.signature",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "輕食/CAFÉ",
|
|
|
+ text: "food_souvenirs_info.light",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "美食街",
|
|
|
+ text: "food_souvenirs_info.courts",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "伴手禮",
|
|
|
+ text: "food_souvenirs_info.souvenir",
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+// 秘境花園觀景台
|
|
|
+let observationList = reactive([
|
|
|
+ {
|
|
|
+ value: "線上購票",
|
|
|
+ text: "observatory_info.tickets.title",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "參觀資訊",
|
|
|
+ text: "observatory_info.visitor.title",
|
|
|
+ },
|
|
|
+ // {
|
|
|
+ // value: "實境景色",
|
|
|
+ // text: "observatory_info.view",
|
|
|
+ // },
|
|
|
+ {
|
|
|
+ value: "前往秘境花園觀景台",
|
|
|
+ text: "observatory_info.garden",
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+// 購物及優惠
|
|
|
+let shoppingList = reactive([
|
|
|
+ {
|
|
|
+ value: "購物品牌查詢",
|
|
|
+ text: "shopping_discounts_info.brands.title",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "國際貴賓卡專屬禮遇",
|
|
|
+ text: "shopping_discounts_info.tourist_card.title",
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+// 購物品牌查詢
|
|
|
+let brandList = reactive([
|
|
|
+ {
|
|
|
+ value: "國際珠寶腕錶",
|
|
|
+ text: "shopping_discounts_info.brands.jewely",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "國際精品",
|
|
|
+ text: "shopping_discounts_info.brands.boutique",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "美妝保養品",
|
|
|
+ text: "shopping_discounts_info.brands.skincare",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "流行服飾",
|
|
|
+ text: "shopping_discounts_info.brands.apparel",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "生活居家/3C",
|
|
|
+ text: "shopping_discounts_info.brands.lifestyle",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "文化創意",
|
|
|
+ text: "shopping_discounts_info.brands.cultural",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "特色品牌",
|
|
|
+ text: "shopping_discounts_info.brands.signature",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "館外店家",
|
|
|
+ text: "shopping_discounts_info.brands.outside",
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+// 服務資訊
|
|
|
+let serviceList = reactive([
|
|
|
+ {
|
|
|
+ title: "service_info.business_hours",
|
|
|
+ depiction: "service_info.business_hours_1",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "service_info.tax_refund",
|
|
|
+ depiction: "service_info.tax_refund_1",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "service_info.toilets",
|
|
|
+ depiction: "service_info.toilets_1",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "service_info.charge",
|
|
|
+ depiction: "service_info.charge_1",
|
|
|
+ },
|
|
|
+ // {
|
|
|
+ // title: "AI 天燈",
|
|
|
+ // depiction: "https://cmm.ai/101-postcard/#/skylanternhome",
|
|
|
+ // },
|
|
|
+]);
|
|
|
+
|
|
|
+// 定位點
|
|
|
+let locationList = reactive([
|
|
|
+ {
|
|
|
+ location: "B1 鼎泰豐", // 編號 1
|
|
|
+ navigation: [
|
|
|
+ {
|
|
|
+ value: "景觀餐廳櫃台",
|
|
|
+ text: "high_rise_restaurant_reception",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "ATM",
|
|
|
+ text: "atm",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "觀景台售票處",
|
|
|
+ text: "observatory_ticket_office",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "鼎泰豐",
|
|
|
+ text: "din_tai_fung",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "捷運",
|
|
|
+ text: "metro_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "超市",
|
|
|
+ text: "supermarket",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "美食街",
|
|
|
+ text: "food_court",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "退稅/外幣兌換櫃台",
|
|
|
+ text: "tax_refund_currency_exchange",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "計程車乘車處",
|
|
|
+ text: "taxi_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "無障礙廁所",
|
|
|
+ text: "accessible_facilities",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "客服(停車折抵)",
|
|
|
+ text: "service_counter_parking_discount",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "哺乳室",
|
|
|
+ text: "nursing_room",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "飲水機",
|
|
|
+ text: "ice_warmed_water_dispenser",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "伴手禮區",
|
|
|
+ text: "snackable_souvenirs",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "廁所",
|
|
|
+ text: "restroom",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "置物櫃",
|
|
|
+ text: "items_and_luggage_storage",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ // navigation: [
|
|
|
+ // "景觀餐廳櫃台",
|
|
|
+ // "ATM",
|
|
|
+ // "觀景台售票處",
|
|
|
+ // "鼎泰豐",
|
|
|
+ // "捷運",
|
|
|
+ // "超市",
|
|
|
+ // "美食街",
|
|
|
+ // "退稅/外幣兌換櫃台",
|
|
|
+ // "計程車乘車處",
|
|
|
+ // "無障礙廁所",
|
|
|
+ // "客服(停車折抵)",
|
|
|
+ // "哺乳室",
|
|
|
+ // "飲水機",
|
|
|
+ // "伴手禮區",
|
|
|
+ // "廁所",
|
|
|
+ // "置物櫃",
|
|
|
+ // ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ location: "B1 中環", // 編號 2
|
|
|
+ navigation: [
|
|
|
+ {
|
|
|
+ value: "景觀餐廳櫃台",
|
|
|
+ text: "high_rise_restaurant_reception",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "ATM",
|
|
|
+ text: "atm",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "觀景台售票處",
|
|
|
+ text: "observatory_ticket_office",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "美食街",
|
|
|
+ text: "food_court",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "伴手禮區",
|
|
|
+ text: "snackable_souvenirs",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "鼎泰豐",
|
|
|
+ text: "din_tai_fung",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "捷運",
|
|
|
+ text: "metro_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "超市",
|
|
|
+ text: "supermarket",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "計程車乘車處",
|
|
|
+ text: "taxi_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "無障礙廁所",
|
|
|
+ text: "accessible_facilities",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ // navigation: [
|
|
|
+ // "景觀餐廳櫃台",
|
|
|
+ // "ATM",
|
|
|
+ // "觀景台售票處",
|
|
|
+ // "美食街",
|
|
|
+ // "伴手禮區",
|
|
|
+ // "鼎泰豐",
|
|
|
+ // "捷運",
|
|
|
+ // "超市",
|
|
|
+ // "計程車乘車處",
|
|
|
+ // "無障礙廁所",
|
|
|
+ // ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ location: "B1 2號電梯", // 編號 3
|
|
|
+ navigation: [
|
|
|
+ {
|
|
|
+ value: "景觀餐廳櫃台",
|
|
|
+ text: "high_rise_restaurant_reception",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "ATM",
|
|
|
+ text: "atm",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "觀景台售票處",
|
|
|
+ text: "observatory_ticket_office",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "客服(退稅櫃台/外幣兌換)",
|
|
|
+ text: "tax_refund_currency_exchange",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "計程車乘車處",
|
|
|
+ text: "taxi_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "無障礙廁所",
|
|
|
+ text: "accessible_facilities",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "超市",
|
|
|
+ text: "supermarket",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "鼎泰豐",
|
|
|
+ text: "din_tai_fung",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "捷運",
|
|
|
+ text: "metro_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "伴手禮區",
|
|
|
+ text: "snackable_souvenirs",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "廁所",
|
|
|
+ text: "restroom",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "置物櫃",
|
|
|
+ text: "items_and_luggage_storage",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "美食街",
|
|
|
+ text: "food_court",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "客服(停車折抵)",
|
|
|
+ text: "service_counter_parking_discount",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "哺乳室",
|
|
|
+ text: "nursing_room",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "飲水機",
|
|
|
+ text: "ice_warmed_water_dispenser",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "廁所",
|
|
|
+ text: "restroom",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ // navigation: [
|
|
|
+ // "景觀餐廳櫃台",
|
|
|
+ // "ATM",
|
|
|
+ // "觀景台售票處",
|
|
|
+ // "客服(退稅櫃台/外幣兌換)",
|
|
|
+ // "計程車乘車處",
|
|
|
+ // "無障礙廁所",
|
|
|
+ // "超市",
|
|
|
+ // "鼎泰豐",
|
|
|
+ // "捷運",
|
|
|
+ // "伴手禮區",
|
|
|
+ // "廁所",
|
|
|
+ // "置物櫃",
|
|
|
+ // "美食街",
|
|
|
+ // "客服(停車折抵)",
|
|
|
+ // "哺乳室",
|
|
|
+ // "飲水機",
|
|
|
+ // "廁所",
|
|
|
+ // ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ location: "B1 4號電梯", // 編號 4
|
|
|
+ navigation: [
|
|
|
+ {
|
|
|
+ value: "景觀餐廳櫃台",
|
|
|
+ text: "high_rise_restaurant_reception",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "ATM",
|
|
|
+ text: "atm",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "觀景台售票處",
|
|
|
+ text: "observatory_ticket_office",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "廁所",
|
|
|
+ text: "restroom",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "置物櫃",
|
|
|
+ text: "items_and_luggage_storage",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "客服(停車折抵)",
|
|
|
+ text: "service_counter_parking_discount",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "哺乳室",
|
|
|
+ text: "nursing_room",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "飲水機",
|
|
|
+ text: "ice_warmed_water_dispenser",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "退稅/外幣兌換櫃台",
|
|
|
+ text: "tax_refund_currency_exchange",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "計程車乘車處",
|
|
|
+ text: "taxi_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "無障礙廁所",
|
|
|
+ text: "accessible_facilities",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "伴手禮區",
|
|
|
+ text: "snackable_souvenirs",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "捷運",
|
|
|
+ text: "metro_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "鼎泰豐",
|
|
|
+ text: "din_tai_fung",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "美食街",
|
|
|
+ text: "food_court",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "超市",
|
|
|
+ text: "supermarket",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ // navigation: [
|
|
|
+ // "景觀餐廳櫃台",
|
|
|
+ // "ATM",
|
|
|
+ // "觀景台售票處",
|
|
|
+ // "廁所",
|
|
|
+ // "置物櫃",
|
|
|
+ // "客服(停車折抵)",
|
|
|
+ // "哺乳室",
|
|
|
+ // "飲水機",
|
|
|
+ // "退稅/外幣兌換櫃台",
|
|
|
+ // "計程車乘車處",
|
|
|
+ // "無障礙廁所",
|
|
|
+ // "伴手禮區",
|
|
|
+ // "捷運",
|
|
|
+ // "鼎泰豐",
|
|
|
+ // "美食街",
|
|
|
+ // "超市",
|
|
|
+ // ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ location: "B1 3號電梯", // 編號 5
|
|
|
+ navigation: [
|
|
|
+ {
|
|
|
+ value: "景觀餐廳櫃台",
|
|
|
+ text: "high_rise_restaurant_reception",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "ATM",
|
|
|
+ text: "atm",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "觀景台售票處",
|
|
|
+ text: "observatory_ticket_office",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "廁所",
|
|
|
+ text: "restroom",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "無障礙廁所",
|
|
|
+ text: "accessible_facilities",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "計程車乘車處",
|
|
|
+ text: "taxi_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "客服(退稅櫃台/外幣兌換)",
|
|
|
+ text: "tax_refund_currency_exchange",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "飲水機",
|
|
|
+ text: "ice_warmed_water_dispenser",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "哺乳室",
|
|
|
+ text: "nursing_room",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "置物櫃",
|
|
|
+ text: "items_and_luggage_storage",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "客服(停車折抵)",
|
|
|
+ text: "service_counter_parking_discount",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "美食街",
|
|
|
+ text: "food_court",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "伴手禮區",
|
|
|
+ text: "snackable_souvenirs",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "鼎泰豐",
|
|
|
+ text: "din_tai_fung",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "捷運",
|
|
|
+ text: "metro_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "超市",
|
|
|
+ text: "supermarket",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ // navigation: [
|
|
|
+ // "景觀餐廳櫃台",
|
|
|
+ // "ATM",
|
|
|
+ // "觀景台售票處",
|
|
|
+ // "廁所",
|
|
|
+ // "無障礙廁所",
|
|
|
+ // "計程車乘車處",
|
|
|
+ // "客服(退稅櫃台/外幣兌換)",
|
|
|
+ // "飲水機",
|
|
|
+ // "哺乳室",
|
|
|
+ // "置物櫃",
|
|
|
+ // "客服(停車折抵)",
|
|
|
+ // "美食街",
|
|
|
+ // "伴手禮區",
|
|
|
+ // "鼎泰豐",
|
|
|
+ // "捷運",
|
|
|
+ // "超市",
|
|
|
+ // ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ location: "1F 信義環", // 編號 6
|
|
|
+ navigation: [
|
|
|
+ {
|
|
|
+ value: "景觀餐廳櫃台",
|
|
|
+ text: "high_rise_restaurant_reception",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "ATM",
|
|
|
+ text: "atm",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "觀景台售票處",
|
|
|
+ text: "observatory_ticket_office",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "廁所",
|
|
|
+ text: "restroom",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "ATM",
|
|
|
+ text: "atm",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "置物櫃",
|
|
|
+ text: "items_and_luggage_storage",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "飲水機",
|
|
|
+ text: "ice_warmed_water_dispenser",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "無障礙廁所",
|
|
|
+ text: "accessible_facilities",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "哺乳室",
|
|
|
+ text: "nursing_room",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "客服(停車折抵)",
|
|
|
+ text: "service_counter_parking_discount",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "退稅/外幣兌換櫃台",
|
|
|
+ text: "tax_refund_currency_exchange",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "伴手禮區",
|
|
|
+ text: "snackable_souvenirs",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "美食街",
|
|
|
+ text: "food_court",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "捷運",
|
|
|
+ text: "metro_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "鼎泰豐",
|
|
|
+ text: "din_tai_fung",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "超市",
|
|
|
+ text: "supermarket",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ // navigation: [
|
|
|
+ // "景觀餐廳櫃台",
|
|
|
+ // "ATM",
|
|
|
+ // "觀景台售票處",
|
|
|
+ // "廁所",
|
|
|
+ // "ATM",
|
|
|
+ // "置物櫃",
|
|
|
+ // "飲水機",
|
|
|
+ // "無障礙廁所",
|
|
|
+ // "哺乳室",
|
|
|
+ // "客服(停車折抵)",
|
|
|
+ // "退稅/外幣兌換櫃台",
|
|
|
+ // "伴手禮區",
|
|
|
+ // "美食街",
|
|
|
+ // "捷運",
|
|
|
+ // "鼎泰豐",
|
|
|
+ // "超市",
|
|
|
+ // ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ location: "1F 3號電梯", // 編號 7
|
|
|
+ navigation: [
|
|
|
+ {
|
|
|
+ value: "景觀餐廳櫃台",
|
|
|
+ text: "high_rise_restaurant_reception",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "ATM",
|
|
|
+ text: "atm",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "觀景台售票處",
|
|
|
+ text: "observatory_ticket_office",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "廁所",
|
|
|
+ text: "restroom",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "置物櫃",
|
|
|
+ text: "items_and_luggage_storage",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "飲水機",
|
|
|
+ text: "ice_warmed_water_dispenser",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "無障礙廁所",
|
|
|
+ text: "accessible_facilities",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "哺乳室",
|
|
|
+ text: "nursing_room",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "客服(停車折抵)",
|
|
|
+ text: "service_counter_parking_discount",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "退稅/外幣兌換櫃台",
|
|
|
+ text: "tax_refund_currency_exchange",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "計程車乘車處",
|
|
|
+ text: "taxi_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "美食街",
|
|
|
+ text: "food_court",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "超市",
|
|
|
+ text: "supermarket",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "捷運",
|
|
|
+ text: "metro_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "伴手禮區",
|
|
|
+ text: "snackable_souvenirs",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ // navigation: [
|
|
|
+ // "景觀餐廳櫃台",
|
|
|
+ // "ATM",
|
|
|
+ // "觀景台售票處",
|
|
|
+ // "廁所",
|
|
|
+ // "置物櫃",
|
|
|
+ // "飲水機",
|
|
|
+ // "無障礙廁所",
|
|
|
+ // "哺乳室",
|
|
|
+ // "客服(停車折抵)",
|
|
|
+ // "退稅/外幣兌換櫃台",
|
|
|
+ // "計程車乘車處",
|
|
|
+ // "美食街",
|
|
|
+ // "超市",
|
|
|
+ // "捷運",
|
|
|
+ // "伴手禮區",
|
|
|
+ // ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ location: "2F 3號電梯", // 編號 8
|
|
|
+ navigation: [
|
|
|
+ {
|
|
|
+ value: "景觀餐廳櫃台",
|
|
|
+ text: "high_rise_restaurant_reception",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "ATM",
|
|
|
+ text: "atm",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "觀景台售票處",
|
|
|
+ text: "observatory_ticket_office",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "廁所",
|
|
|
+ text: "restroom",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "置物櫃",
|
|
|
+ text: "items_and_luggage_storage",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "飲水機",
|
|
|
+ text: "ice_warmed_water_dispenser",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "無障礙廁所",
|
|
|
+ text: "accessible_facilities",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "哺乳室",
|
|
|
+ text: "nursing_room",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "計程車乘車處",
|
|
|
+ text: "taxi_station",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "退稅/外幣兌換櫃台",
|
|
|
+ text: "tax_refund_currency_exchange",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "客服(停車折抵)",
|
|
|
+ text: "service_counter_parking_discount",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ // navigation: [
|
|
|
+ // "景觀餐廳櫃台",
|
|
|
+ // "ATM",
|
|
|
+ // "觀景台售票處",
|
|
|
+ // "廁所",
|
|
|
+ // "置物櫃",
|
|
|
+ // "飲水機",
|
|
|
+ // "無障礙廁所",
|
|
|
+ // "哺乳室",
|
|
|
+ // "計程車乘車處",
|
|
|
+ // "退稅/外幣兌換櫃台",
|
|
|
+ // "客服(停車折抵)",
|
|
|
+ // ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ location: "89F 觀景台", // 編號 9
|
|
|
+ navigation: [
|
|
|
+ {
|
|
|
+ value: "景觀餐廳櫃台",
|
|
|
+ text: "high_rise_restaurant_reception",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "ATM",
|
|
|
+ text: "atm",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "客服",
|
|
|
+ text: "service_counter",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "91F戶外區",
|
|
|
+ text: "91f_outdoor_area",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "廁所",
|
|
|
+ text: "restroom",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "101F排隊處",
|
|
|
+ text: "101f_queue_area",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ // navigation: [
|
|
|
+ // "景觀餐廳櫃台",
|
|
|
+ // "ATM",
|
|
|
+ // "客服",
|
|
|
+ // "91F戶外區",
|
|
|
+ // "廁所",
|
|
|
+ // "101F排隊處",
|
|
|
+ // ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ location: "88F 觀景台", // 編號 10
|
|
|
+ navigation: [
|
|
|
+ {
|
|
|
+ value: "景觀餐廳櫃台",
|
|
|
+ text: "high_rise_restaurant_reception",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "ATM",
|
|
|
+ text: "atm",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "廁所",
|
|
|
+ text: "restroom",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "飲水機",
|
|
|
+ text: "ice_warmed_water_dispenser",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "哺乳室",
|
|
|
+ text: "nursing_room",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ // navigation: ["景觀餐廳櫃台", "ATM", "廁所", "飲水機", "哺乳室"],
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+// 平面圖
|
|
|
+let mapList = reactive([
|
|
|
+ {
|
|
|
+ title: "B1 平面圖",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "1F 平面圖",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "2F 平面圖",
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+let assignLocation = ref("當前位置"); // 指定定位點
|
|
|
+let assignNavigationList = ref(null); // 指定導覽點列表
|
|
|
+// let assignLocationFloor = ref(null); // 指定定位點樓層
|
|
|
+// let assignNavigation = ref(null); // 指定導覽點
|
|
|
+// let navigationBtn = reactive(["B1", "1F", "2F", "4F", "5F", "89F", "91F"]);
|
|
|
+
|
|
|
+let arVideo = ref(null);
|
|
|
+let arVideoDialog = ref(false);
|
|
|
+
|
|
|
+// 取得 AR 導覽影片
|
|
|
+async function getArviews(route, text, type = "") {
|
|
|
+ let url;
|
|
|
+ let lang = getLang();
|
|
|
+ console.log("text >>>", text);
|
|
|
+
|
|
|
+ if (type === "garden") {
|
|
|
+ console.log("route", route);
|
|
|
+ let start;
|
|
|
+ // if (route === "B1 鼎泰豐旁 數位屏幕") {
|
|
|
+ // start = "B1 鼎泰豐";
|
|
|
+ // } else if (route === "B1 松智藍梯梯廳") {
|
|
|
+ // start = "B1 3號電梯";
|
|
|
+ // }
|
|
|
+
|
|
|
+ switch (route) {
|
|
|
+ case "B1 鼎泰豐旁 數位屏幕":
|
|
|
+ start = "B1 鼎泰豐";
|
|
|
+ break;
|
|
|
+ case "B1 中環WOW屏幕":
|
|
|
+ start = "B1 中環";
|
|
|
+ break;
|
|
|
+ case "B1 信義橘梯梯廳":
|
|
|
+ start = "B1 2號電梯";
|
|
|
+ break;
|
|
|
+ case "B1 信義綠梯梯廳":
|
|
|
+ start = "B1 4號電梯";
|
|
|
+ break;
|
|
|
+ case "B1 松智藍梯梯廳":
|
|
|
+ start = "B1 3號電梯";
|
|
|
+ break;
|
|
|
+ case "1F 信義綠梯出入口":
|
|
|
+ start = "1F 信義環";
|
|
|
+ break;
|
|
|
+ case "1F 松智梯廳":
|
|
|
+ start = "1F 3號電梯";
|
|
|
+ break;
|
|
|
+ case "2F 松智藍梯梯廳":
|
|
|
+ start = "2F 3號電梯";
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ url = `https://cmm.ai:9101/arviews?language=${lang}&start=${start}&end=觀景台售票處`;
|
|
|
+ } else {
|
|
|
+ url = `https://cmm.ai:9101/arviews?language=${lang}&start=${assignLocation.value}&end=${route}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("url", url);
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: text,
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await axios.get(url);
|
|
|
+ console.log("AR 導覽影片", response);
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "ar_views",
|
|
|
+ author: "ai",
|
|
|
+ body: response.data,
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.log("error", error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 取得定位點 & 導覽點
|
|
|
+function changeLocation(item) {
|
|
|
+ assignLocation.value = item;
|
|
|
+
|
|
|
+ console.log("定位點:", item);
|
|
|
+
|
|
|
+ let assign = locationList.filter((e) => e.location === item);
|
|
|
+ console.log("assign", assign);
|
|
|
+
|
|
|
+ assignNavigationList.value = assign[0].navigation;
|
|
|
+
|
|
|
+ // assignLocation.value = item.location;
|
|
|
+ // assignNavigationList.value = item.navigation;
|
|
|
+
|
|
|
+ console.log("assignNavigationList.value", assignNavigationList.value);
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("select_navigation_location"),
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "navigation",
|
|
|
+ author: "ai",
|
|
|
+ body: assignNavigationList.value,
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// 顯示平面圖
|
|
|
+function assignMapImg(item) {
|
|
|
+ console.log("顯示平面圖 item", item);
|
|
|
+ let name;
|
|
|
+ if (item.title === "B1 平面圖") {
|
|
|
+ name = "All_b1";
|
|
|
+ } else if (item.title === "1F 平面圖") {
|
|
|
+ name = "All_1";
|
|
|
+ } else if (item.title === "2F 平面圖") {
|
|
|
+ name = "All_2";
|
|
|
+ }
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "map_img",
|
|
|
+ author: "ai",
|
|
|
+ body: name,
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// 動態引入視頻文件
|
|
|
+const videoSources = ref([]); // 開場白影片(中)
|
|
|
+const videoSourcesEn = ref([]); // 開場白影片(英)
|
|
|
+const videoMuteSources = ref([]); // 點頭影片(靜音)
|
|
|
+const videoSpeakSources = ref([]); // 動嘴型影片
|
|
|
+
|
|
|
+const loadVideoSources = async () => {
|
|
|
+ // 本地端影片路徑
|
|
|
+ // const start1 = await import("@/assets/video/start_1.mp4");
|
|
|
+ // const start2 = await import("@/assets/video/start_2.mp4");
|
|
|
+ // const mute_1 = await import("@/assets/video/mute_1.mp4");
|
|
|
+ // const mute_2 = await import("@/assets/video/mute_2.mp4");
|
|
|
+ // const speak_1 = await import("@/assets/video/speak_1.mp4");
|
|
|
+ // const speak_2 = await import("@/assets/video/speak_2.mp4");
|
|
|
+ // videoSpeakSources.value = [speak_1.default, speak_2.default];
|
|
|
+
|
|
|
+ videoSources.value = [
|
|
|
+ "https://cmm.ai/101-ai-chatbot-new/video/start_1.mp4",
|
|
|
+ "https://cmm.ai/101-ai-chatbot-new/video/start_2.mp4",
|
|
|
+ ];
|
|
|
+ videoSourcesEn.value = [
|
|
|
+ "https://cmm.ai/101-ai-chatbot-new/video/start_en_1.mp4",
|
|
|
+ "https://cmm.ai/101-ai-chatbot-new/video/start_en_2.mp4",
|
|
|
+ ];
|
|
|
+ videoMuteSources.value = [
|
|
|
+ "https://cmm.ai/101-ai-chatbot-new/video/mute_1.mp4",
|
|
|
+ "https://cmm.ai/101-ai-chatbot-new/video/mute_2.mp4",
|
|
|
+ ];
|
|
|
+ videoSpeakSources.value = [
|
|
|
+ "https://cmm.ai/101-ai-chatbot-new/video/speak_1.mp4",
|
|
|
+ "https://cmm.ai/101-ai-chatbot-new/video/speak_2.mp4",
|
|
|
+ ];
|
|
|
+};
|
|
|
+
|
|
|
+let videoSrc = ref("");
|
|
|
+let hideAnchorPrologue = ref(false); // 顯示開場白 or 點頭影片
|
|
|
+let videoIndex = ref(null); // 影片編號
|
|
|
+
|
|
|
+// 選擇類別
|
|
|
+async function selectCategory(value, index) {
|
|
|
+ assignCategory.value = value;
|
|
|
+ assignCategoryIndex.value = index;
|
|
|
+ console.log("selectCategory index", assignCategoryIndex.value);
|
|
|
+ console.log("selectCategory value", assignCategory.value);
|
|
|
+
|
|
|
+ if (value === "叫出真人客服") {
|
|
|
+ showAnchor.value = true;
|
|
|
+
|
|
|
+ if (currentAudio.value && isAudioPlaying.value) {
|
|
|
+ // currentAudio.value.addEventListener("ended", onAudioEnded); // 監聽音訊播放結束
|
|
|
+ // setTimeout(() => {
|
|
|
+ // video.value.play();
|
|
|
+ // }, 0);
|
|
|
+ // return;
|
|
|
+ handleVoice("pause"); // 暫停音訊
|
|
|
+ }
|
|
|
+
|
|
|
+ // 隨機取得影片路徑
|
|
|
+ await loadVideoSources();
|
|
|
+
|
|
|
+ // 主播開場白只顯示一次
|
|
|
+ if (!hideAnchorPrologue.value) {
|
|
|
+ let lang = getLang();
|
|
|
+ let sources;
|
|
|
+
|
|
|
+ if (lang === "ch") {
|
|
|
+ sources = videoSources.value;
|
|
|
+ } else if (lang === "en") {
|
|
|
+ sources = videoSourcesEn.value;
|
|
|
+ }
|
|
|
+
|
|
|
+ const randomIndex = Math.floor(Math.random() * sources.length);
|
|
|
+ videoSrc.value = sources[randomIndex];
|
|
|
+ hideAnchorPrologue.value = true;
|
|
|
+ videoIndex.value = randomIndex + 1;
|
|
|
+
|
|
|
+ // const randomIndex = Math.floor(Math.random() * videoSources.value.length);
|
|
|
+ // videoSrc.value = videoSources.value[randomIndex];
|
|
|
+ // hideAnchorPrologue.value = true;
|
|
|
+ // videoIndex.value = randomIndex + 1;
|
|
|
+ } else {
|
|
|
+ const randomIndex = Math.floor(
|
|
|
+ Math.random() * videoMuteSources.value.length
|
|
|
+ );
|
|
|
+ videoSrc.value = videoMuteSources.value[randomIndex];
|
|
|
+ videoIndex.value = randomIndex + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 播放影片
|
|
|
+ setTimeout(() => {
|
|
|
+ videoPlay();
|
|
|
+ }, 0);
|
|
|
+
|
|
|
+ // const randomIndex = Math.floor(Math.random() * videoSources.value.length);
|
|
|
+ // videoSrc.value = videoSources.value[randomIndex];
|
|
|
+ }
|
|
|
+ // else if (value === "隱藏真人客服") {
|
|
|
+ // showAnchor.value = false;
|
|
|
+ // video.value.pause();
|
|
|
+ // menuList[0][0].text = "customer_show";
|
|
|
+ // menuList[0][0].value = "叫出真人客服";
|
|
|
+ // }
|
|
|
+ else if (value === "秘境花園觀景台") {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("observatory_info.tickets.purchase"),
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "observation_deck",
|
|
|
+ author: "ai",
|
|
|
+ body: observationList,
|
|
|
+ });
|
|
|
+ } else if (value === "位置導引") {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("qr_code_scan_prompt"),
|
|
|
+ });
|
|
|
+
|
|
|
+ // messages.value.push({
|
|
|
+ // label: "map_img",
|
|
|
+ // author: "ai",
|
|
|
+ // body: mapList,
|
|
|
+ // });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "location",
|
|
|
+ author: "ai",
|
|
|
+ body: locationList,
|
|
|
+ });
|
|
|
+ } else if (value === "美食/伴手禮") {
|
|
|
+ getAd("美食伴手禮");
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("food_souvenirs_info.searching"),
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "dining",
|
|
|
+ author: "ai",
|
|
|
+ body: diningList,
|
|
|
+ });
|
|
|
+ } else if (value === "購物及優惠") {
|
|
|
+ getAd("購物及優惠");
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "shopping",
|
|
|
+ author: "ai",
|
|
|
+ body: shoppingList,
|
|
|
+ });
|
|
|
+ } else if (value === "服務資訊") {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("service_info.inquiry_prompt"),
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "service",
|
|
|
+ author: "ai",
|
|
|
+ body: serviceList,
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+let secondaryAd = ref({}); // 隨機廣告
|
|
|
+let secondaryAdShow = ref(false);
|
|
|
+
|
|
|
+// 取得隨機廣告 (美食伴手禮/購物及優惠)
|
|
|
+async function getAd(type) {
|
|
|
+ let lang = getLang();
|
|
|
+ let url = `https://cmm.ai:9101/ad/${type}?language=${lang}`;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await axios.get(url);
|
|
|
+ console.log("response", response);
|
|
|
+ secondaryAd.value = response.data.data;
|
|
|
+ // secondaryAdShow.value = true; // 取消蓋版廣告
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "brand",
|
|
|
+ author: "ai",
|
|
|
+ body: [
|
|
|
+ {
|
|
|
+ info: response.data.data,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.log("error", error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 服務資訊回覆
|
|
|
+function handleService(title, depiction) {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "user",
|
|
|
+ body: title,
|
|
|
+ });
|
|
|
+
|
|
|
+ if (title !== "AI 天燈") {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: depiction,
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ window.location.href = depiction;
|
|
|
+ // window.open(depiction, "_blank"); // 另開天燈頁面
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 參觀資訊回覆
|
|
|
+function handleVisit(title, depiction) {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "user",
|
|
|
+ body: title,
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: depiction,
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// 計算使用次數
|
|
|
+async function handleClick() {
|
|
|
+ let url = "https://cmm.ai:9101/click";
|
|
|
+ try {
|
|
|
+ const response = await axios.get(url);
|
|
|
+ console.log("Click", response);
|
|
|
+ } catch (error) {
|
|
|
+ console.log("error", error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+let langList = reactive([
|
|
|
+ {
|
|
|
+ lang: "中文",
|
|
|
+ value: "zh-tw",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ lang: "English",
|
|
|
+ value: "en-us",
|
|
|
+ },
|
|
|
+ // {
|
|
|
+ // lang: "日本語",
|
|
|
+ // value: "ja-jp",
|
|
|
+ // },
|
|
|
+ // {
|
|
|
+ // lang: "한국어",
|
|
|
+ // value: "ko-kr",
|
|
|
+ // },
|
|
|
+]);
|
|
|
+
|
|
|
+const btnList = reactive([
|
|
|
+ {
|
|
|
+ title: "observation_deck",
|
|
|
+ value: "秘境花園觀景台",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "location_guide",
|
|
|
+ value: "位置導引",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "food_souvenirs",
|
|
|
+ value: "美食伴手禮",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "shopping_discounts",
|
|
|
+ value: "購物及優惠",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "service_information",
|
|
|
+ value: "服務資訊",
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+function getImageUrl(name) {
|
|
|
+ return new URL(`../assets/img/icon/${name}`, import.meta.url).href;
|
|
|
+}
|
|
|
+
|
|
|
+let showAnchor = ref(false); // AI 主播影片
|
|
|
+let videoContent = ref(null); // 主播影片區塊
|
|
|
+
|
|
|
+// 動態更改主播區塊位置
|
|
|
+// watch(showAnchor, () => {
|
|
|
+// console.log("showAnchor", showAnchor.value);
|
|
|
+// nextTick(() => {
|
|
|
+// changeAnchorTop();
|
|
|
+// });
|
|
|
+// });
|
|
|
+
|
|
|
+// watch(hideMenu, () => {
|
|
|
+// nextTick(() => {
|
|
|
+// changeAnchorTop();
|
|
|
+// });
|
|
|
+// });
|
|
|
+
|
|
|
+// function changeAnchorTop() {
|
|
|
+// const menuOffsetTop = menu.value.offsetTop;
|
|
|
+// videoContent.value.style.top = `${menuOffsetTop - 170}px`;
|
|
|
+// }
|
|
|
+
|
|
|
+const menuList = reactive([
|
|
|
+ [
|
|
|
+ {
|
|
|
+ imgSrc: "素材-05.png",
|
|
|
+ text: "customer_show",
|
|
|
+ value: "叫出真人客服",
|
|
|
+ },
|
|
|
+ { imgSrc: "素材-06.png", text: "service_information", value: "服務資訊" },
|
|
|
+ { imgSrc: "素材-07.png", text: "shopping_discounts", value: "購物及優惠" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ imgSrc: "素材-08.png",
|
|
|
+ text: "observation_deck",
|
|
|
+ value: "秘境花園觀景台",
|
|
|
+ },
|
|
|
+ { imgSrc: "素材-09.png", text: "food_souvenirs", value: "美食/伴手禮" },
|
|
|
+ { imgSrc: "素材-10.png", text: "location_guide", value: "位置導引" },
|
|
|
+ ],
|
|
|
+]);
|
|
|
+
|
|
|
+// 美食伴手禮 or 購物及優惠類別篩選
|
|
|
+async function findBrand(value) {
|
|
|
+ console.log("findBrand", value);
|
|
|
+
|
|
|
+ if (value === "館外店家") {
|
|
|
+ value = "館外";
|
|
|
+ }
|
|
|
+
|
|
|
+ let lang = getLang();
|
|
|
+ let url = `https://cmm.ai:9101/find_brand?keyword=${value}&language=${lang}`;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await axios.get(url);
|
|
|
+ console.log("response", response);
|
|
|
+
|
|
|
+ // response.data.data.map(
|
|
|
+ // (item) => (item.info.tags = JSON.parse(item.info.tags))
|
|
|
+ // );
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "brand",
|
|
|
+ author: "ai",
|
|
|
+ body: response.data.data,
|
|
|
+ });
|
|
|
+
|
|
|
+ assignCategoryIndex.value = null;
|
|
|
+ assignCategory.value = "";
|
|
|
+
|
|
|
+ // messages.value.push({
|
|
|
+ // label: "text",
|
|
|
+ // author: "ai",
|
|
|
+ // body: "請選擇問題類型:",
|
|
|
+ // });
|
|
|
+
|
|
|
+ // messages.value.push({
|
|
|
+ // label: "btn-list",
|
|
|
+ // author: "ai",
|
|
|
+ // body: "",
|
|
|
+ // });
|
|
|
+ } catch (error) {
|
|
|
+ console.log("error", error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 處理電話格式
|
|
|
+// function getPhoneNumber(phoneString) {
|
|
|
+// const parts = phoneString.split("\n");
|
|
|
+// return parts[1];
|
|
|
+// }
|
|
|
+
|
|
|
+// 取得線上購票
|
|
|
+async function getStaticTickets(type) {
|
|
|
+ let url = `https://cmm.ai:9101/static_tickets?is_Chinese=${type}`;
|
|
|
+
|
|
|
+ let info = {
|
|
|
+ buttonList: [], // 按鈕
|
|
|
+ ticketList: [], // 票券
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await axios.get(url);
|
|
|
+ console.log("線上購票", response.data.result);
|
|
|
+
|
|
|
+ response.data.result.map((item) => info.ticketList.push(item));
|
|
|
+ console.log("info", info);
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "ticket",
|
|
|
+ author: "ai",
|
|
|
+ body: info,
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.log("error", error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 秘境花園觀景台對話
|
|
|
+function handleObservationDialog(value) {
|
|
|
+ console.log("value", value);
|
|
|
+
|
|
|
+ if (value === "線上購票") {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("observatory_info.tickets.id_card"),
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "check",
|
|
|
+ author: "ai",
|
|
|
+ body: [
|
|
|
+ {
|
|
|
+ value: "是",
|
|
|
+ text: "observatory_info.tickets.yes",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: "否",
|
|
|
+ text: "observatory_info.tickets.no",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ });
|
|
|
+ } else if (value === "是" || value === "否") {
|
|
|
+ if (value === "是") {
|
|
|
+ getStaticTickets("1");
|
|
|
+ } else if (value === "否") {
|
|
|
+ getStaticTickets("0");
|
|
|
+ }
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("observatory_info.tickets.ticket_type"),
|
|
|
+ });
|
|
|
+ } else if (value === "參觀資訊") {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("observatory_info.visitor.question"),
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("observatory_info.visitor.faq"),
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "visit",
|
|
|
+ author: "ai",
|
|
|
+ body: visitList,
|
|
|
+ });
|
|
|
+ } else if (value === "實境景色") {
|
|
|
+ // window.location.href = "https://www.youtube.com/watch?v=RVV00FZbeH0";
|
|
|
+ window.open("https://www.youtube.com/watch?v=RVV00FZbeH0", "_blank"); // 另開頁面
|
|
|
+ } else if (value === "前往秘境花園觀景台") {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("select_location"),
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "garden_route",
|
|
|
+ author: "ai",
|
|
|
+ body: [
|
|
|
+ "B1 鼎泰豐旁 數位屏幕",
|
|
|
+ "B1 中環WOW屏幕",
|
|
|
+ "B1 信義橘梯梯廳",
|
|
|
+ "B1 信義綠梯梯廳",
|
|
|
+ "B1 松智藍梯梯廳",
|
|
|
+ "1F 信義綠梯出入口",
|
|
|
+ "1F 松智梯廳",
|
|
|
+ "2F 松智藍梯梯廳",
|
|
|
+ ],
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 購物及優惠對話
|
|
|
+function handleShoppingDialog(value) {
|
|
|
+ console.log("value", value);
|
|
|
+
|
|
|
+ if (value === "購物品牌查詢") {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: "請選擇品牌類別",
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "shopping_brand",
|
|
|
+ author: "ai",
|
|
|
+ body: brandList,
|
|
|
+ });
|
|
|
+ } else if (value === "國際貴賓卡專屬禮遇") {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "user",
|
|
|
+ body: "國際貴賓卡專屬禮遇",
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("shopping_discounts_info.tourist_card.content"),
|
|
|
+ });
|
|
|
+ } else if (value === "參觀資訊") {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("observatory_info.visitor.question"),
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "ai",
|
|
|
+ body: t("observatory_info.visitor.faq"),
|
|
|
+ });
|
|
|
+
|
|
|
+ messages.value.push({
|
|
|
+ label: "visit",
|
|
|
+ author: "ai",
|
|
|
+ body: visitList,
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+let visitList = reactive([
|
|
|
+ {
|
|
|
+ title: "observatory_info.visitor.events_q",
|
|
|
+ depiction: "observatory_info.visitor.events_a",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "observatory_info.visitor.ball_q",
|
|
|
+ depiction: "observatory_info.visitor.ball_a",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "observatory_info.visitor.accessible_q",
|
|
|
+ depiction: "observatory_info.visitor.accessible_a",
|
|
|
+ },
|
|
|
+]);
|
|
|
+
|
|
|
+// 判斷價格是否包含 $ 符號
|
|
|
+function formatPrice(price) {
|
|
|
+ if (price === "") {
|
|
|
+ return "";
|
|
|
+ } else {
|
|
|
+ if (price.includes("$")) {
|
|
|
+ return price;
|
|
|
+ } else {
|
|
|
+ return `$${price}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+let currentAudio = ref(null); // 當前音訊
|
|
|
+let audioDuration = ref(null); // 音訊秒數
|
|
|
+
|
|
|
+// 文字轉語音 (TTS)
|
|
|
+async function handleTTS(message) {
|
|
|
+ console.log("handleTTS", message);
|
|
|
+ let audioLang; // 音訊語言
|
|
|
+ let lang = localStorage.getItem("lang");
|
|
|
+ console.log("lang", lang);
|
|
|
+
|
|
|
+ switch (lang) {
|
|
|
+ case "zh-tw":
|
|
|
+ audioLang = "cmn-TW";
|
|
|
+ break;
|
|
|
+ case "en-us":
|
|
|
+ audioLang = "en-US";
|
|
|
+ break;
|
|
|
+ case "ja-jp":
|
|
|
+ audioLang = "ja-JP";
|
|
|
+ break;
|
|
|
+ case "ko-kr":
|
|
|
+ audioLang = "ko-KR";
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // let url = `https://bf18-61-230-0-215.ngrok-free.app/ttsTry/tts_try?message=${message}&type=101`;
|
|
|
+ let url = `https://bf18-61-230-0-215.ngrok-free.app/gcp/text-to-speech?language_code=${audioLang}&gender=female`;
|
|
|
+
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append("text", message);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await axios.post(url, formData, { responseType: "blob" });
|
|
|
+ console.log("TTS response", response);
|
|
|
+
|
|
|
+ const blob = new Blob([response.data], { type: "audio/mp3" });
|
|
|
+ const audioUrl = URL.createObjectURL(blob);
|
|
|
+ console.log("audioUrl", audioUrl);
|
|
|
+ cutVideo();
|
|
|
+
|
|
|
+ // 取得 mp3 音訊秒數
|
|
|
+ // audio.addEventListener("loadedmetadata", function () {
|
|
|
+ // audioDuration.value = audio.duration;
|
|
|
+ // cutVideo();
|
|
|
+ // });
|
|
|
+
|
|
|
+ // 暫停當前音訊
|
|
|
+ if (currentAudio.value) {
|
|
|
+ currentAudio.value.pause();
|
|
|
+ currentAudio.value.currentTime = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 播放音檔
|
|
|
+ currentAudio.value = new Audio(audioUrl);
|
|
|
+ } catch (error) {
|
|
|
+ console.log("error", error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+let videoLoading = ref(false);
|
|
|
+let isAudioPlaying = ref(false); // 音訊播放狀態
|
|
|
+
|
|
|
+// 取得語音回覆 mp4
|
|
|
+async function cutVideo() {
|
|
|
+ videoSrc.value = videoSpeakSources.value[videoIndex.value - 1];
|
|
|
+ video.value.load(); // 重新讀取影片
|
|
|
+
|
|
|
+ // let duration = Math.floor(audioDuration.value); // 音訊秒數
|
|
|
+
|
|
|
+ // 影片和音訊加載完成後播放
|
|
|
+ video.value.oncanplay = () => {
|
|
|
+ setTimeout(() => {
|
|
|
+ // 監聽音訊播放結束
|
|
|
+ currentAudio.value.addEventListener("ended", onAudioEnded);
|
|
|
+
|
|
|
+ // 監聽音訊播放狀態
|
|
|
+ currentAudio.value.addEventListener("play", onAudioPlay);
|
|
|
+ currentAudio.value.addEventListener("pause", onAudioPause);
|
|
|
+
|
|
|
+ video.value.play(); // 播放影片
|
|
|
+ currentAudio.value.currentTime = 0; // 重置時間
|
|
|
+ currentAudio.value.play(); // 播放音訊
|
|
|
+ isVideoPause.value = true;
|
|
|
+ videoLoading.value = false;
|
|
|
+ }, 500);
|
|
|
+ };
|
|
|
+
|
|
|
+ // video.value.currentTime = 0;
|
|
|
+ // video.value.play(); // 播放影片
|
|
|
+ // currentAudio.value.play(); // 播放音訊
|
|
|
+ // isVideoPause.value = true;
|
|
|
+ // videoLoading.value = false;
|
|
|
+
|
|
|
+ // setTimeout(()=>{
|
|
|
+ // video.value.play(); // 播放影片
|
|
|
+ // },300)
|
|
|
+
|
|
|
+ // // 監聽音訊播放狀態
|
|
|
+ // currentAudio.value.addEventListener("play", onAudioPlay);
|
|
|
+ // currentAudio.value.addEventListener("pause", onAudioPause);
|
|
|
+
|
|
|
+ // 監聽音訊播放結束
|
|
|
+ // currentAudio.value.addEventListener("ended", onAudioEnded);
|
|
|
+
|
|
|
+ // if (video.value.currentTime >= duration) {
|
|
|
+ // video.value.pause();
|
|
|
+ // }
|
|
|
+}
|
|
|
+
|
|
|
+// 音訊結束後暫停影片播放
|
|
|
+const onAudioEnded = () => {
|
|
|
+ video.value.pause();
|
|
|
+};
|
|
|
+
|
|
|
+// 判斷音訊是否為播放狀態
|
|
|
+const onAudioPlay = () => {
|
|
|
+ isAudioPlaying.value = true;
|
|
|
+ console.log("isAudioPlaying.value", isAudioPlaying.value);
|
|
|
+};
|
|
|
+
|
|
|
+const onAudioPause = () => {
|
|
|
+ isAudioPlaying.value = false;
|
|
|
+ console.log("isAudioPlaying.value", isAudioPlaying.value);
|
|
|
+};
|
|
|
+
|
|
|
+const audioURL = ref(null);
|
|
|
+const audioFile = ref(null); // 音訊檔案
|
|
|
+
|
|
|
+let recordTime = ref(0); // 錄音時間
|
|
|
+let isRecording = ref(false); // 錄音狀態
|
|
|
+let timer;
|
|
|
+
|
|
|
+// 語音轉文字
|
|
|
+async function handleAudioToText() {
|
|
|
+ isRecording.value = false;
|
|
|
+ let audioLang; // 音訊語言
|
|
|
+ let lang = localStorage.getItem("lang");
|
|
|
+ console.log("lang", lang);
|
|
|
+
|
|
|
+ switch (lang) {
|
|
|
+ case "zh-tw":
|
|
|
+ audioLang = "cmn-Hant-TW";
|
|
|
+ break;
|
|
|
+ case "en-us":
|
|
|
+ audioLang = "en-US";
|
|
|
+ break;
|
|
|
+ case "ja-jp":
|
|
|
+ audioLang = "ja-JP";
|
|
|
+ break;
|
|
|
+ case "ko-kr":
|
|
|
+ audioLang = "ko-KR";
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // let url = `http://172.104.93.163:9880/whisper/${audioLang}/`;
|
|
|
+ // let url = `https://bf18-61-230-0-215.ngrok-free.app/whisper/${audioLang}/`;
|
|
|
+ let url = `https://bf18-61-230-0-215.ngrok-free.app/gcp/speech-to-text?language_code=${audioLang}`;
|
|
|
+
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append("file", audioFile.value);
|
|
|
+
|
|
|
+ try {
|
|
|
+ console.log("audioFile.value", audioFile.value);
|
|
|
+ const response = await axios.post(url, formData);
|
|
|
+ console.log("語音轉文字 response", response);
|
|
|
+ // showAnchor.value = false; // 關閉主播視窗
|
|
|
+
|
|
|
+ userMessage.value = response.data[0];
|
|
|
+ // handleTTS(userMessage.value); // 取得語音回覆
|
|
|
+
|
|
|
+ if (response.data[0] && response.data[0] !== "") {
|
|
|
+ sendMessage(); // 傳送使用者訊息
|
|
|
+ } else {
|
|
|
+ if (showAnchor.value) {
|
|
|
+ alert("語音辨識有誤,請重新錄製。");
|
|
|
+ videoLoading.value = false;
|
|
|
+ return;
|
|
|
+ } else {
|
|
|
+ messages.value.push({
|
|
|
+ label: "text",
|
|
|
+ author: "user",
|
|
|
+ body: "語音辨識有誤,請重新錄製。",
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.log("error", error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 語音轉文字 (使用 recorder-core 錄音)
|
|
|
+let rec, wave;
|
|
|
+
|
|
|
+// 調用 open 請求錄音權限
|
|
|
+let recOpen = function (success) {
|
|
|
+ rec = Recorder({
|
|
|
+ type: "mp3",
|
|
|
+ sampleRate: 16000,
|
|
|
+ bitRate: 16,
|
|
|
+ onProcess: function (
|
|
|
+ buffers,
|
|
|
+ powerLevel,
|
|
|
+ bufferDuration,
|
|
|
+ bufferSampleRate,
|
|
|
+ newBufferIdx,
|
|
|
+ asyncEnd
|
|
|
+ ) {
|
|
|
+ wave &&
|
|
|
+ wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ rec.open(
|
|
|
+ function () {
|
|
|
+ if (Recorder.WaveView) wave = Recorder.WaveView({ elem: ".recwave" });
|
|
|
+ success && success();
|
|
|
+ },
|
|
|
+ function (msg, isUserNotAllow) {
|
|
|
+ // 使用者未授權或不支援
|
|
|
+ console.log((isUserNotAllow ? "UserNotAllow," : "") + "無法錄音:" + msg);
|
|
|
+ }
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+/** 開始錄音 **/
|
|
|
+function recStart() {
|
|
|
+ togglePause("pause"); // 暫停影片音訊
|
|
|
+ // 需先呼叫 recOpen() 開啟錄音後才能調用 start、stop 方法
|
|
|
+ console.log("開始錄音");
|
|
|
+
|
|
|
+ recOpen(function () {
|
|
|
+ isRecording.value = true;
|
|
|
+
|
|
|
+ // 開始計時
|
|
|
+ timer = setInterval(() => {
|
|
|
+ recordTime.value += 1;
|
|
|
+ }, 1000);
|
|
|
+
|
|
|
+ rec.start();
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+/** 結束錄音 **/
|
|
|
+function recStop() {
|
|
|
+ videoLoading.value = true;
|
|
|
+ rec.stop(
|
|
|
+ function (blob, duration) {
|
|
|
+ // 利用 URL 產生本地檔案位址,不用時需要 revokeObjectURL
|
|
|
+ let localUrl = (window.URL || webkitURL).createObjectURL(blob); // 該 url 只能本地端使用 (例如給 audio.src 進行播放,或是給 a.href download 進行下載)
|
|
|
+ console.log(blob, localUrl, "時長:" + duration + "ms");
|
|
|
+ // rec.close(); // 釋放錄音資源 (若不釋放系統或瀏覽器將持續提示在錄音中)
|
|
|
+ // rec = null;
|
|
|
+
|
|
|
+ // 將 Blob 轉換為 File 對象
|
|
|
+ audioFile.value = new File([blob], "recording.mp3", {
|
|
|
+ type: "audio/mp3",
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log("audioFile", audioFile.value);
|
|
|
+
|
|
|
+ // 自動播放(測試用)
|
|
|
+ // const audio = document.createElement("audio");
|
|
|
+ // audio.src = localUrl;
|
|
|
+ // audio.autoplay = true;
|
|
|
+ // audio
|
|
|
+ // .play()
|
|
|
+ // .then(() => {
|
|
|
+ // console.log("Audio is playing automatically.");
|
|
|
+ // })
|
|
|
+ // .catch((error) => {
|
|
|
+ // console.error("Error playing audio:", error);
|
|
|
+ // });
|
|
|
+
|
|
|
+ rec.close(); // 釋放錄音資源 (若不釋放系統或瀏覽器將持續提示在錄音中)
|
|
|
+ rec = null;
|
|
|
+
|
|
|
+ if (recordTime.value !== 0) {
|
|
|
+ handleAudioToText(); // 語音轉文字
|
|
|
+ } else {
|
|
|
+ isRecording.value = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ clearInterval(timer); // 清空計時秒數
|
|
|
+ recordTime.value = 0;
|
|
|
+ },
|
|
|
+ function (msg) {
|
|
|
+ console.log("錄音失敗:" + msg);
|
|
|
+ rec.close(); // 可以透過 stop 方法的第 3 個參數來自動呼叫 close
|
|
|
+ rec = null;
|
|
|
+ }
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+// 關閉主播視窗 (結束錄音)
|
|
|
+function closeRec() {
|
|
|
+ video.value.pause();
|
|
|
+ isVideoPause.value = true;
|
|
|
+ recordTime.value = 0;
|
|
|
+ clearInterval(timer); // 清空計時秒數
|
|
|
+ if (isRecording.value) {
|
|
|
+ recStop();
|
|
|
+ }
|
|
|
+ showAnchor.value = false;
|
|
|
+}
|
|
|
+
|
|
|
+// watch(showAnchor, (val) => {
|
|
|
+// if (val) {
|
|
|
+// isVideoPause.value = true;
|
|
|
+// }
|
|
|
+// });
|
|
|
+
|
|
|
+let isVideoPause = ref(true);
|
|
|
+
|
|
|
+// AI 主播影片播放 & 暫停
|
|
|
+function togglePause(val) {
|
|
|
+ if (val === "pause") {
|
|
|
+ // video.value.pause();
|
|
|
+ isVideoPause.value = false;
|
|
|
+
|
|
|
+ if (video.value) {
|
|
|
+ video.value.pause();
|
|
|
+ }
|
|
|
+ if (currentAudio.value) {
|
|
|
+ currentAudio.value.pause(); // 暫停音訊
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // video.value.play();
|
|
|
+ isVideoPause.value = true;
|
|
|
+
|
|
|
+ if (video.value) {
|
|
|
+ video.value.play();
|
|
|
+ }
|
|
|
+ if (currentAudio.value) {
|
|
|
+ currentAudio.value.play(); // 播放音訊
|
|
|
+ currentAudio.value.addEventListener("ended", onAudioEnded);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 語音暫停
|
|
|
+function handleVoice(state) {
|
|
|
+ if (currentAudio.value) {
|
|
|
+ if (state === "pause") {
|
|
|
+ currentAudio.value.pause();
|
|
|
+ isVideoPause.value = false;
|
|
|
+ } else {
|
|
|
+ currentAudio.value.play();
|
|
|
+ isVideoPause.value = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
</script>
|
|
|
|
|
|
-<template>
|
|
|
- <!-- <Navbar /> -->
|
|
|
- <Chat />
|
|
|
+<template>
|
|
|
+ <Navbar v-if="!isLanguagePage" />
|
|
|
+
|
|
|
+ <div v-if="isLanguagePage" class="lang-content">
|
|
|
+ <div class="btn-list">
|
|
|
+ <button
|
|
|
+ v-for="(item, index) in langList"
|
|
|
+ :key="index"
|
|
|
+ @click="chooseLang(item.value)"
|
|
|
+ class="main-btn"
|
|
|
+ >
|
|
|
+ {{ item.lang }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else-if="!isLanguagePage && !showAnchor" class="main-containar">
|
|
|
+ <!-- <div class="video-content">
|
|
|
+ <video
|
|
|
+ ref="video"
|
|
|
+ preload
|
|
|
+ playsinline
|
|
|
+ :style="{ opacity: !isTTSVideo ? '1' : '0' }"
|
|
|
+ >
|
|
|
+ <source src="../assets/video/start_1.mp4" type="video/mp4" />
|
|
|
+ Your browser does not support the video tag.
|
|
|
+ </video>
|
|
|
+
|
|
|
+ <video
|
|
|
+ ref="ttsVideo"
|
|
|
+ preload
|
|
|
+ playsinline
|
|
|
+ :style="{ opacity: isTTSVideo ? '1' : '0' }"
|
|
|
+ >
|
|
|
+ <source :src="ttsVideoSrc" type="video/mp4" />
|
|
|
+
|
|
|
+ Your browser does not support the video tag.
|
|
|
+ </video>
|
|
|
+
|
|
|
+ <button @click="handleVideo(isTTSVideo)" class="control-btn">
|
|
|
+ <img v-if="!videoPause" src="../assets/img/pause-button.png" alt="" />
|
|
|
+ <img v-else src="../assets/img/play-button.png" alt="" />
|
|
|
+ </button>
|
|
|
+ </div> -->
|
|
|
+
|
|
|
+ <!-- <Navbar /> -->
|
|
|
+ <div v-if="chatLoading" class="d-flex justify-center pt-5">
|
|
|
+ <v-progress-circular color="primary" indeterminate></v-progress-circular>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-else
|
|
|
+ class="chat-content"
|
|
|
+ :class="{ 'hide-menu': hideMenu }"
|
|
|
+ :style="{ paddingBottom: !hideMenu ? menuHeight + 'px' : '50px' }"
|
|
|
+ >
|
|
|
+ <!-- <div class="headline">
|
|
|
+ <h1>{{ t("title") }}</h1>
|
|
|
+ <button @click="isRotate = !isRotate">
|
|
|
+ <img
|
|
|
+ :class="{ rotate: isRotate }"
|
|
|
+ src="../assets/img/angles-up-solid.svg"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ </button>
|
|
|
+ </div> -->
|
|
|
+
|
|
|
+ <!-- 購票介面 -->
|
|
|
+ <TicketPurchase />
|
|
|
+
|
|
|
+ <router-link
|
|
|
+ to="/brand-search"
|
|
|
+ style="color: #fff; text-decoration: none"
|
|
|
+ class="ps-3"
|
|
|
+ >館外品牌測試頁面</router-link
|
|
|
+ >
|
|
|
+
|
|
|
+ <!-- 語音暫停按鈕 -->
|
|
|
+ <div v-if="currentAudio" class="voice-btn">
|
|
|
+ <button v-if="isAudioPlaying" @click="togglePause('pause')">
|
|
|
+ <img src="../assets/img/pause-button.png" alt="" />
|
|
|
+ <p>語音</p>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <button v-else @click="togglePause('play')">
|
|
|
+ <img src="../assets/img/play-button.png" alt="" />
|
|
|
+ <p>語音</p>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <section
|
|
|
+ ref="chatArea"
|
|
|
+ class="chat-area"
|
|
|
+ :class="{ 'hide-menu': hideMenu }"
|
|
|
+ >
|
|
|
+ <div v-for="message in messages" class="message-content">
|
|
|
+ <p
|
|
|
+ v-if="message.label === 'text'"
|
|
|
+ class="message animate__animated"
|
|
|
+ :class="{
|
|
|
+ 'message-out': message.author === 'user',
|
|
|
+ 'message-in': message.author !== 'user',
|
|
|
+ animate__fadeInRight: message.author === 'user',
|
|
|
+ animate__fadeInLeft: message.author !== 'user',
|
|
|
+ }"
|
|
|
+ v-html="message.body"
|
|
|
+ ></p>
|
|
|
+
|
|
|
+ <!-- 問題類別 -->
|
|
|
+ <!-- <div v-else-if="message.label === 'btn-list'">
|
|
|
+ <ul class="btn-list">
|
|
|
+ <li v-for="(btn, index) in btnList">
|
|
|
+ <button
|
|
|
+ :class="{ active: index === assignCategoryIndex }"
|
|
|
+ @click="selectCategory(btn.value, index)"
|
|
|
+ >
|
|
|
+ {{ t(`${btn.title}`) }}
|
|
|
+ </button>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div> -->
|
|
|
+
|
|
|
+ <!-- 秘境花園觀景台 -->
|
|
|
+ <div v-else-if="message.label === 'observation_deck'">
|
|
|
+ <swiper
|
|
|
+ :slidesPerView="'auto'"
|
|
|
+ :spaceBetween="20"
|
|
|
+ :modules="modules"
|
|
|
+ class="btn-swiper"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body">
|
|
|
+ <div class="btn-container">
|
|
|
+ <button @click="handleObservationDialog(item.value)">
|
|
|
+ {{ t(item.text) }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 確認按鈕 -->
|
|
|
+ <div v-else-if="message.label === 'check'">
|
|
|
+ <swiper
|
|
|
+ :slidesPerView="'auto'"
|
|
|
+ :spaceBetween="20"
|
|
|
+ :modules="modules"
|
|
|
+ class="btn-swiper"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body">
|
|
|
+ <div class="btn-container">
|
|
|
+ <button @click="handleObservationDialog(item.value)">
|
|
|
+ {{ t(item.text) }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 參觀資訊 -->
|
|
|
+ <div v-else-if="message.label === 'visit'">
|
|
|
+ <swiper
|
|
|
+ :slidesPerView="'auto'"
|
|
|
+ :spaceBetween="20"
|
|
|
+ :modules="modules"
|
|
|
+ class="btn-swiper"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body">
|
|
|
+ <div class="btn-container">
|
|
|
+ <button
|
|
|
+ @click="handleVisit(t(item.title), t(item.depiction))"
|
|
|
+ >
|
|
|
+ {{ t(item.title) }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 位置導引 (定位點) -->
|
|
|
+ <div v-else-if="message.label === 'location'">
|
|
|
+ <!-- <v-select
|
|
|
+ @update:modelValue="changeLocation"
|
|
|
+ v-model="assignLocation"
|
|
|
+ label="當前位置"
|
|
|
+ :items="locationList"
|
|
|
+ item-title="location"
|
|
|
+ item-value="location"
|
|
|
+ variant="outlined"
|
|
|
+ density="comfortable"
|
|
|
+ hide-details
|
|
|
+ color="primary"
|
|
|
+ class="mt-5 me-15"
|
|
|
+ :menu-props="{ maxHeight: '155px' }"
|
|
|
+ ></v-select> -->
|
|
|
+
|
|
|
+ <!-- 掃描 QR Code -->
|
|
|
+ <v-btn @click="qrCodeDialog = true" color="primary" class="mt-5">
|
|
|
+ {{ t("tap_to_start_scan") }}
|
|
|
+ </v-btn>
|
|
|
+
|
|
|
+ <v-dialog v-model="qrCodeDialog" width="auto">
|
|
|
+ <v-card max-width="400">
|
|
|
+ <v-card-text>
|
|
|
+ <div v-if="qrCodeLoading" class="d-flex justify-center pt-5">
|
|
|
+ <v-progress-circular
|
|
|
+ color="primary"
|
|
|
+ indeterminate
|
|
|
+ ></v-progress-circular>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <qrcode-stream
|
|
|
+ v-show="!qrCodeLoading"
|
|
|
+ @detect="onDetect"
|
|
|
+ @camera-on="onInit"
|
|
|
+ ></qrcode-stream>
|
|
|
+ </v-card-text>
|
|
|
+
|
|
|
+ <template v-slot:actions>
|
|
|
+ <v-btn
|
|
|
+ class="ms-auto"
|
|
|
+ text="關閉"
|
|
|
+ @click="qrCodeDialog = false"
|
|
|
+ ></v-btn>
|
|
|
+ </template>
|
|
|
+ </v-card>
|
|
|
+ </v-dialog>
|
|
|
+
|
|
|
+ <!-- <div class="d-flex">
|
|
|
+ <v-menu location="top">
|
|
|
+ <template v-slot:activator="{ props }">
|
|
|
+ <v-btn class="mt-5 py-2" color="primary" dark v-bind="props">
|
|
|
+ <p class="me-2">{{ assignLocation }}</p>
|
|
|
+ <svg
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ width="18"
|
|
|
+ height="18"
|
|
|
+ fill="currentColor"
|
|
|
+ class="bi bi-caret-down-fill pt-1"
|
|
|
+ viewBox="0 0 16 16"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ d="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ </v-btn>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <v-list style="max-height: 155px; overflow-y: auto">
|
|
|
+ <v-list-item
|
|
|
+ v-for="(item, index) in locationList"
|
|
|
+ :key="index"
|
|
|
+ >
|
|
|
+ <v-list-item-title @click="changeLocation(item)">
|
|
|
+ {{ index + 1 }}. {{ item.location }}
|
|
|
+ </v-list-item-title>
|
|
|
+ </v-list-item>
|
|
|
+ </v-list>
|
|
|
+ </v-menu>
|
|
|
+
|
|
|
+ <v-menu location="top">
|
|
|
+ <template v-slot:activator="{ props }">
|
|
|
+ <v-btn
|
|
|
+ class="ms-5 mt-5 py-2"
|
|
|
+ color="primary"
|
|
|
+ dark
|
|
|
+ v-bind="props"
|
|
|
+ >
|
|
|
+ <p class="me-2">查看平面圖</p>
|
|
|
+ <svg
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ width="18"
|
|
|
+ height="18"
|
|
|
+ fill="currentColor"
|
|
|
+ class="bi bi-caret-down-fill pt-1"
|
|
|
+ viewBox="0 0 16 16"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ d="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ </v-btn>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <v-list style="max-height: 155px; overflow-y: auto">
|
|
|
+ <v-list-item v-for="(item, index) in mapList" :key="index">
|
|
|
+ <v-list-item-title @click="assignMapImg(item)">
|
|
|
+ {{ item.title }}
|
|
|
+ </v-list-item-title>
|
|
|
+ </v-list-item>
|
|
|
+ </v-list>
|
|
|
+ </v-menu>
|
|
|
+ </div> -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 定位點平面圖 -->
|
|
|
+ <!-- <div v-else-if="message.label === 'map_img'" class="mt-5">
|
|
|
+ <img
|
|
|
+ class="map-img"
|
|
|
+ :class="{ 'show-anchor': showAnchor }"
|
|
|
+ :src="`../src/assets/img/map/${message.body}.webp`"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ </div> -->
|
|
|
+
|
|
|
+ <!-- 位置導引 (導覽點) -->
|
|
|
+ <div v-else-if="message.label === 'navigation'">
|
|
|
+ <!-- <v-select
|
|
|
+ @update:modelValue="changeLocation"
|
|
|
+ v-model="assignNavigation"
|
|
|
+ label="導覽位置"
|
|
|
+ :items="assignNavigationList"
|
|
|
+ variant="solo"
|
|
|
+ hide-details
|
|
|
+ class="bg-white mt-4"
|
|
|
+ ></v-select> -->
|
|
|
+ <swiper
|
|
|
+ :slidesPerView="'auto'"
|
|
|
+ :spaceBetween="20"
|
|
|
+ :modules="modules"
|
|
|
+ class="btn-swiper"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body">
|
|
|
+ <div class="btn-container">
|
|
|
+ <button
|
|
|
+ @click="
|
|
|
+ getArviews(item.value, t(`navigation.${item.text}`))
|
|
|
+ "
|
|
|
+ >
|
|
|
+ {{ t(`navigation.${item.text}`) }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 秘境花園觀景台 -->
|
|
|
+ <div v-else-if="message.label === 'garden_route'">
|
|
|
+ <!-- <v-select
|
|
|
+ @update:modelValue="changeLocation"
|
|
|
+ v-model="assignNavigation"
|
|
|
+ label="導覽位置"
|
|
|
+ :items="assignNavigationList"
|
|
|
+ variant="solo"
|
|
|
+ hide-details
|
|
|
+ class="bg-white mt-4"
|
|
|
+ ></v-select> -->
|
|
|
+ <swiper
|
|
|
+ :slidesPerView="'auto'"
|
|
|
+ :spaceBetween="20"
|
|
|
+ :modules="modules"
|
|
|
+ class="btn-swiper"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body">
|
|
|
+ <div class="btn-container">
|
|
|
+ <button @click="getArviews(item, item, 'garden')">
|
|
|
+ {{ item }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- AR 導覽影片 -->
|
|
|
+ <div v-else-if="message.label === 'ar_views'">
|
|
|
+ <div
|
|
|
+ class="message animate__animated"
|
|
|
+ :class="{
|
|
|
+ 'message-out': message.author === 'user',
|
|
|
+ 'message-in': message.author !== 'user',
|
|
|
+ animate__fadeInRight: message.author === 'user',
|
|
|
+ animate__fadeInLeft: message.author !== 'user',
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <p v-html="message.body.words"></p>
|
|
|
+
|
|
|
+ <button @click="arVideoDialog = true" class="ar-link my-3">
|
|
|
+ 進行 AR 導覽
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <v-dialog v-model="arVideoDialog" width="auto">
|
|
|
+ <v-card class="ar-card">
|
|
|
+ <v-card-title class="mt-3"> AR 導覽 </v-card-title>
|
|
|
+
|
|
|
+ <v-card-text class="pa-0">
|
|
|
+ <a-scene>
|
|
|
+ <a-assets>
|
|
|
+ <video
|
|
|
+ id="vr-video"
|
|
|
+ ref="arVideo"
|
|
|
+ :src="message.body.url"
|
|
|
+ autoplay
|
|
|
+ crossorigin="anonymous"
|
|
|
+ ecrossorigin="anonymous"
|
|
|
+ ></video>
|
|
|
+ </a-assets>
|
|
|
+
|
|
|
+ <a-sky src="#vr-video"></a-sky>
|
|
|
+ </a-scene>
|
|
|
+ </v-card-text>
|
|
|
+
|
|
|
+ <template v-slot:actions>
|
|
|
+ <v-btn
|
|
|
+ class="ms-auto"
|
|
|
+ text="關閉"
|
|
|
+ @click="arVideoDialog = false"
|
|
|
+ ></v-btn>
|
|
|
+ </template>
|
|
|
+ </v-card>
|
|
|
+ </v-dialog>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 美食伴手禮 -->
|
|
|
+ <div v-else-if="message.label === 'dining'">
|
|
|
+ <!-- <ul class="btn-options">
|
|
|
+ <li v-for="item in message.body">
|
|
|
+ <button @click="findBrand(item.value)">
|
|
|
+ {{ t(item.text) }}
|
|
|
+ </button>
|
|
|
+ </li>
|
|
|
+ </ul> -->
|
|
|
+
|
|
|
+ <swiper
|
|
|
+ :slidesPerView="'auto'"
|
|
|
+ :spaceBetween="20"
|
|
|
+ :modules="modules"
|
|
|
+ class="btn-swiper"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body">
|
|
|
+ <div class="btn-container">
|
|
|
+ <button @click="findBrand(item.value)">
|
|
|
+ {{ t(item.text) }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 購物及優惠 -->
|
|
|
+ <div v-else-if="message.label === 'shopping'">
|
|
|
+ <!-- <ul class="btn-options">
|
|
|
+ <li v-for="item in message.body">
|
|
|
+ <button @click="handleShoppingDialog(item.value)">
|
|
|
+ {{ t(item.text) }}
|
|
|
+ </button>
|
|
|
+ </li>
|
|
|
+ </ul> -->
|
|
|
+
|
|
|
+ <swiper
|
|
|
+ :slidesPerView="'auto'"
|
|
|
+ :spaceBetween="20"
|
|
|
+ :modules="modules"
|
|
|
+ class="btn-swiper"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body">
|
|
|
+ <div class="btn-container">
|
|
|
+ <button @click="handleShoppingDialog(item.value)">
|
|
|
+ {{ t(item.text) }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 購物品牌查詢 -->
|
|
|
+ <div v-else-if="message.label === 'shopping_brand'">
|
|
|
+ <!-- <ul class="btn-options">
|
|
|
+ <li v-for="item in message.body">
|
|
|
+ <button @click="findBrand(item.value)">
|
|
|
+ {{ item.value }}
|
|
|
+ </button>
|
|
|
+ </li>
|
|
|
+ </ul> -->
|
|
|
+
|
|
|
+ <swiper
|
|
|
+ :slidesPerView="'auto'"
|
|
|
+ :spaceBetween="20"
|
|
|
+ :modules="modules"
|
|
|
+ class="btn-swiper"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body">
|
|
|
+ <div class="btn-container">
|
|
|
+ <button @click="findBrand(item.value)">
|
|
|
+ {{ t(item.text) }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 服務資訊 (常見問題) -->
|
|
|
+ <div v-else-if="message.label === 'service'">
|
|
|
+ <swiper
|
|
|
+ :slidesPerView="'auto'"
|
|
|
+ :spaceBetween="20"
|
|
|
+ :modules="modules"
|
|
|
+ class="btn-swiper"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body">
|
|
|
+ <div class="btn-container">
|
|
|
+ <button
|
|
|
+ @click="handleService(t(item.title), t(item.depiction))"
|
|
|
+ >
|
|
|
+ {{ t(item.title) }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 推薦資訊 -->
|
|
|
+ <div v-else-if="message.label === 'ticket'" class="ticket-item">
|
|
|
+ <!-- 按鈕 -->
|
|
|
+ <swiper
|
|
|
+ v-if="message.body.buttonList.length"
|
|
|
+ :slidesPerView="'auto'"
|
|
|
+ :spaceBetween="20"
|
|
|
+ :modules="modules"
|
|
|
+ class="btn-swiper"
|
|
|
+ :style="{
|
|
|
+ marginBottom: message.body.ticketList.length ? '35px' : '0px',
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body.buttonList">
|
|
|
+ <div class="btn-container">
|
|
|
+ <button @click="setBtnValue(item.info.value)">
|
|
|
+ {{ item.info.key }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+
|
|
|
+ <!-- 票券 -->
|
|
|
+ <swiper
|
|
|
+ v-if="message.body.ticketList.length"
|
|
|
+ :slidesPerView="1"
|
|
|
+ :navigation="true"
|
|
|
+ :modules="modules"
|
|
|
+ class="ticket-slide"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body.ticketList">
|
|
|
+ <div class="slide-item">
|
|
|
+ <img
|
|
|
+ class="cover-img"
|
|
|
+ :src="item.info.img || item.info.cover_img"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ <section>
|
|
|
+ <h3 class="title">
|
|
|
+ {{ item.info.name || item.info.title }}
|
|
|
+ </h3>
|
|
|
+ <div class="price-info">
|
|
|
+ <span>{{ item.info.floor || item.info.price }}</span>
|
|
|
+ <div class="link-btn">
|
|
|
+ <a
|
|
|
+ :href="item.info.website_url || item.info.url"
|
|
|
+ target="_blank"
|
|
|
+ >
|
|
|
+ {{ t("ctaGoUrl") }}
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <p class="description">
|
|
|
+ {{ item.info.content || item.info.description }}
|
|
|
+ </p>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 品牌資訊 -->
|
|
|
+ <div v-else-if="message.label === 'brand'" class="ticket-item">
|
|
|
+ <!-- 按鈕 -->
|
|
|
+ <swiper
|
|
|
+ v-if="message.body.buttonList?.length"
|
|
|
+ :slidesPerView="'auto'"
|
|
|
+ :spaceBetween="20"
|
|
|
+ :modules="modules"
|
|
|
+ class="btn-swiper"
|
|
|
+ :style="{
|
|
|
+ marginBottom: message.body.ticketList.length ? '35px' : '0px',
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body.buttonList">
|
|
|
+ <div class="btn-container">
|
|
|
+ <button @click="setBtnValue(item.info.value)">
|
|
|
+ {{ item.info.key }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+
|
|
|
+ <!-- 票券 -->
|
|
|
+ <swiper
|
|
|
+ v-if="message.body.ticketList?.length"
|
|
|
+ :slidesPerView="1"
|
|
|
+ :navigation="true"
|
|
|
+ :modules="modules"
|
|
|
+ class="ticket-slide"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body.ticketList">
|
|
|
+ <div class="slide-item">
|
|
|
+ <img
|
|
|
+ class="cover-img aa"
|
|
|
+ :src="item.info.img || item.info.cover_img"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ <section>
|
|
|
+ <h3 class="title">
|
|
|
+ {{ item.info.name || item.info.title }}
|
|
|
+ </h3>
|
|
|
+ <div class="price-info">
|
|
|
+ <span>{{ item.info.floor || item.info.price }}</span>
|
|
|
+ <div class="link-btn">
|
|
|
+ <a :href="item.info.url" target="_blank">
|
|
|
+ {{ t("ctaGoUrl") }}
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <p class="description">
|
|
|
+ {{ item.info.content || item.info.description }}
|
|
|
+ </p>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+
|
|
|
+ <!-- 美食/伴手禮 -->
|
|
|
+ <swiper
|
|
|
+ v-if="message.body.length"
|
|
|
+ :slidesPerView="1"
|
|
|
+ :navigation="true"
|
|
|
+ :modules="modules"
|
|
|
+ class="ticket-slide"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body">
|
|
|
+ <div class="slide-item">
|
|
|
+ <img
|
|
|
+ class="cover-img"
|
|
|
+ :src="item.info.img || item.info.cover_img"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ <section>
|
|
|
+ <h3 class="title">
|
|
|
+ {{ item.info.name || item.info.title }}
|
|
|
+ </h3>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="item.info.phone !== ''"
|
|
|
+ class="d-flex align-center mt-3 mb-5"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ src="../assets/img/phone-solid.svg"
|
|
|
+ width="15"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ <p class="ms-2">
|
|
|
+ {{ item.info.phone }}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="price-info">
|
|
|
+ <span>{{ item.info.floor || item.info.price }}</span>
|
|
|
+ <div class="link-btn">
|
|
|
+ <a :href="item.info.url" target="_blank">
|
|
|
+ {{ t("ctaGoUrl") }}
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <p class="description">
|
|
|
+ {{ item.info.content || item.info.description }}
|
|
|
+ </p>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+
|
|
|
+ <!-- <swiper
|
|
|
+ v-if="message.body.length"
|
|
|
+ :slidesPerView="'auto'"
|
|
|
+ :spaceBetween="20"
|
|
|
+ :modules="modules"
|
|
|
+ >
|
|
|
+ <swiper-slide v-for="item in message.body">
|
|
|
+ <img
|
|
|
+ class="cover-img"
|
|
|
+ :src="item.info.img || item.info.cover_img"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ <section>
|
|
|
+ <h3 class="title">{{ item.info.name || item.info.title }}</h3>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="item.info.phone !== ''"
|
|
|
+ class="d-flex align-center mt-3 mb-5"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ src="../assets/img/phone-solid.svg"
|
|
|
+ width="15"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ <p class="ms-2">
|
|
|
+ {{ getPhoneNumber(item.info.phone) }}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="price-info">
|
|
|
+ <span>{{ item.info.floor || item.info.price }}</span>
|
|
|
+ <div class="link-btn">
|
|
|
+ <a :href="item.info.url" target="_blank">
|
|
|
+ {{ t("ctaGoUrl") }}
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <p class="description">
|
|
|
+ {{ item.info.content || item.info.description }}
|
|
|
+ </p>
|
|
|
+ </section>
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper> -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 推薦資訊 -->
|
|
|
+ <div v-else-if="message.label === 'recommend'" class="recommend-item">
|
|
|
+ <img class="cover-img" :src="message.body.cover_img" alt="" />
|
|
|
+ <section>
|
|
|
+ <h3 class="title">{{ message.body.title }}</h3>
|
|
|
+ <p class="description">{{ message.body.description }}</p>
|
|
|
+ <div class="link-btn">
|
|
|
+ <a
|
|
|
+ :href="message.body.store_info_url || message.body.url"
|
|
|
+ target="_blank"
|
|
|
+ >
|
|
|
+ {{ t("ctaBuyNow") }}
|
|
|
+ </a>
|
|
|
+ <a :href="message.body.website_url" target="_blank"> 官網 </a>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <p class="date">{{ message.body.date }} 限時搶購</p>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 店家資訊 -->
|
|
|
+ <div v-else-if="message.label === 'store'" class="store-item">
|
|
|
+ <p class="headline">店家資訊</p>
|
|
|
+ <div class="cover-img">
|
|
|
+ <img :src="message.body.cover_img" alt="" />
|
|
|
+ <h3 class="title">{{ message.body.title }}</h3>
|
|
|
+ </div>
|
|
|
+ <section>
|
|
|
+ <div class="info">
|
|
|
+ <p>
|
|
|
+ <img
|
|
|
+ src="../assets/img/location-dot-solid.svg"
|
|
|
+ width="15"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ {{ message.body.location }}
|
|
|
+ </p>
|
|
|
+ <p>
|
|
|
+ <img src="../assets/img/phone-solid.svg" width="15" alt="" />
|
|
|
+ {{ message.body.contact }}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <p class="description">{{ message.body.description }}</p>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 套票資訊 -->
|
|
|
+ <div v-else-if="message.label === 'ticket'" class="ticket-item">
|
|
|
+ <img class="cover-img" :src="message.body.cover_img" alt="" />
|
|
|
+ <section>
|
|
|
+ <h3 class="title">{{ message.body.title }}</h3>
|
|
|
+ <div class="link-btn">
|
|
|
+ <a :href="message.body.store_info_url" target="_blank">
|
|
|
+ 線上預約
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ <p class="description">{{ message.body.description }}</p>
|
|
|
+ <span class="price-item">{{ message.body.price }}</span>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 對話輸入框 -->
|
|
|
+ <!-- <form
|
|
|
+ @submit.prevent="sendMessage('out')"
|
|
|
+ class="chat-inputs"
|
|
|
+ :class="{ 'd-none': !hideMenu }"
|
|
|
+ >
|
|
|
+ <button @click="hideMenu = false" class="menu-btn">
|
|
|
+ <img src="../assets/img/icon/素材-02.png" alt="" width="50" />
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <input
|
|
|
+ v-model="userMessage"
|
|
|
+ type="text"
|
|
|
+ placeholder="Type a message..."
|
|
|
+ />
|
|
|
+ <button type="submit" class="submit">
|
|
|
+ <img width="20" src="../assets/img/paper-plane-solid.svg" alt="" />
|
|
|
+ </button>
|
|
|
+ </form> -->
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- 廣告輪播 -->
|
|
|
+ <!-- <div class="slider-content">
|
|
|
+ <swiper
|
|
|
+ :slidesPerView="1"
|
|
|
+ :autoplay="{
|
|
|
+ delay: 5000,
|
|
|
+ disableOnInteraction: false,
|
|
|
+ }"
|
|
|
+ :modules="modules"
|
|
|
+ >
|
|
|
+ <swiper-slide>
|
|
|
+ <img src="../assets/img/banner-1.jpg" alt="" />
|
|
|
+ </swiper-slide>
|
|
|
+ <swiper-slide>
|
|
|
+ <img src="../assets/img/banner-2.jpg" alt="" />
|
|
|
+ </swiper-slide>
|
|
|
+ </swiper>
|
|
|
+ </div> -->
|
|
|
+
|
|
|
+ <!-- AI 主播影片 -->
|
|
|
+ <!-- <div
|
|
|
+ ref="videoContent"
|
|
|
+ v-show="showAnchor"
|
|
|
+ class="video-content"
|
|
|
+ :class="{ 'video-down': hideMenu }"
|
|
|
+ >
|
|
|
+ <div v-show="!isTTSVideo">
|
|
|
+ <video ref="video" preload playsinline>
|
|
|
+ <source src="../assets/video/start_1.mp4" type="video/mp4" />
|
|
|
+ Your browser does not support the video tag.
|
|
|
+ </video>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-show="isTTSVideo">
|
|
|
+ <video ref="ttsVideo" preload playsinline>
|
|
|
+ <source :src="ttsVideoSrc" type="video/mp4" />
|
|
|
+
|
|
|
+ Your browser does not support the video tag.
|
|
|
+ </video>
|
|
|
+ </div>
|
|
|
+ </div> -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部選單 -->
|
|
|
+ <div ref="menu" class="menu">
|
|
|
+ <!-- AI 主播影片 -->
|
|
|
+ <!-- <div
|
|
|
+ v-show="showAnchor"
|
|
|
+ class="video-content"
|
|
|
+ :class="{ 'video-down': hideMenu }"
|
|
|
+ >
|
|
|
+ <div v-show="!isTTSVideo">
|
|
|
+ <video ref="video" preload playsinline>
|
|
|
+ <source src="../assets/video/start_1.mp4" type="video/mp4" />
|
|
|
+ Your browser does not support the video tag.
|
|
|
+ </video>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-show="isTTSVideo">
|
|
|
+ <video ref="ttsVideo" preload playsinline>
|
|
|
+ <source
|
|
|
+ :src="`https://bf18-61-230-0-215.ngrok-free.app/${ttsVideoSrc}`"
|
|
|
+ type="video/mp4"
|
|
|
+ />
|
|
|
+
|
|
|
+ Your browser does not support the video tag.
|
|
|
+ </video>
|
|
|
+ </div>
|
|
|
+ </div> -->
|
|
|
+
|
|
|
+ <div class="menu-table" :class="{ 'hide-table': hideMenu }">
|
|
|
+ <table class="mt-3">
|
|
|
+ <tbody>
|
|
|
+ <tr v-for="(row, rowIndex) in menuList" :key="rowIndex">
|
|
|
+ <td v-for="(item, itemIndex) in row" :key="itemIndex">
|
|
|
+ <button @click="selectCategory(item.value, itemIndex)">
|
|
|
+ <img :src="getImageUrl(item.imgSrc)" alt="" class="icon" />
|
|
|
+ <p>{{ t(`${item.text}`) }}</p>
|
|
|
+ </button>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- <table class="mt-3">
|
|
|
+ <tbody>
|
|
|
+ <tr v-for="(row, rowIndex) in menuList" :key="rowIndex">
|
|
|
+ <td v-for="(item, itemIndex) in row" :key="itemIndex">
|
|
|
+ <button @click="selectCategory(item.value, itemIndex)">
|
|
|
+ <img :src="getImageUrl(item.imgSrc)" alt="" class="icon" />
|
|
|
+ <p>{{ t(`${item.text}`) }}</p>
|
|
|
+ </button>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table> -->
|
|
|
+
|
|
|
+ <div class="d-flex align-center position-relative">
|
|
|
+ <div class="position-absolute">
|
|
|
+ <button
|
|
|
+ v-if="!showInput"
|
|
|
+ @click="
|
|
|
+ hideMenu = true;
|
|
|
+ showInput = true;
|
|
|
+ "
|
|
|
+ class="ms-3 pt-1"
|
|
|
+ >
|
|
|
+ <img src="../assets/img/icon/素材-04.png" alt="" width="35" />
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <button
|
|
|
+ v-else
|
|
|
+ @click="
|
|
|
+ hideMenu = false;
|
|
|
+ showInput = false;
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ src="../assets/img/icon/素材-02.png"
|
|
|
+ alt=""
|
|
|
+ width="50"
|
|
|
+ class="pt-2"
|
|
|
+ />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="w-100 d-flex align-center justify-center">
|
|
|
+ <button
|
|
|
+ v-if="!showInput"
|
|
|
+ @click="
|
|
|
+ hideMenu = true;
|
|
|
+ showInput = true;
|
|
|
+ "
|
|
|
+ class="d-flex align-center question-btn"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ class="me-2"
|
|
|
+ src="../assets/img/icon/素材-03.png"
|
|
|
+ alt=""
|
|
|
+ width="45"
|
|
|
+ />
|
|
|
+ {{ t("question") }}
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <!-- 對話輸入框 -->
|
|
|
+ <form
|
|
|
+ v-else
|
|
|
+ @submit.prevent="sendMessage('text')"
|
|
|
+ class="chat-inputs"
|
|
|
+ :class="{ 'd-none': !showInput }"
|
|
|
+ >
|
|
|
+ <!-- <button @click="hideMenu = false" class="menu-btn">
|
|
|
+ <img src="../assets/img/icon/素材-02.png" alt="" width="50" />
|
|
|
+ </button> -->
|
|
|
+
|
|
|
+ <input
|
|
|
+ v-model="userMessage"
|
|
|
+ type="text"
|
|
|
+ placeholder="Type a message..."
|
|
|
+ />
|
|
|
+ <button type="submit" class="submit">
|
|
|
+ <img
|
|
|
+ width="20"
|
|
|
+ src="../assets/img/paper-plane-solid.svg"
|
|
|
+ alt=""
|
|
|
+ />
|
|
|
+ </button>
|
|
|
+ </form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- AI 主播語音轉文字 -->
|
|
|
+ <div v-else class="anchor-voice">
|
|
|
+ <div v-show="showAnchor" class="video-content">
|
|
|
+ <div>
|
|
|
+ <video ref="video" preload playsinline>
|
|
|
+ <source :src="videoSrc" type="video/mp4" />
|
|
|
+ <!-- <source src="../assets/video/start_1.mp4" type="video/mp4" /> -->
|
|
|
+ Your browser does not support the video tag.
|
|
|
+ </video>
|
|
|
+
|
|
|
+ <div v-if="videoLoading" class="video-progress">
|
|
|
+ <v-progress-circular
|
|
|
+ color="primary"
|
|
|
+ indeterminate
|
|
|
+ ></v-progress-circular>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- <div v-show="isTTSVideo">
|
|
|
+ <video ref="ttsVideo" preload playsinline>
|
|
|
+ <source
|
|
|
+ :src="` ${ttsVideoSrc}`"
|
|
|
+ type="video/mp4"
|
|
|
+ />
|
|
|
+
|
|
|
+ Your browser does not support the video tag.
|
|
|
+ </video>
|
|
|
+ </div> -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="control-item">
|
|
|
+ <!-- <p class="text-center">連接中…</p> -->
|
|
|
+
|
|
|
+ <div class="d-flex flex-column align-center">
|
|
|
+ <audio v-if="audioURL" :src="audioURL" controls></audio>
|
|
|
+ <!-- <p
|
|
|
+ v-if="!isRecording"
|
|
|
+ class="mb-3 text-center"
|
|
|
+ v-html="t('system_construction')"
|
|
|
+ ></p> -->
|
|
|
+ <p v-if="!isRecording" class="mb-3">點選以進行錄音</p>
|
|
|
+ <p v-else class="mb-3">錄音中:{{ recordTime }} 秒</p>
|
|
|
+ <!-- 錄音按鈕 -->
|
|
|
+ <v-btn
|
|
|
+ v-if="!isRecording"
|
|
|
+ @click="recStart"
|
|
|
+ icon="mdi-circle"
|
|
|
+ size="large"
|
|
|
+ >
|
|
|
+ <v-icon icon="mdi-circle" color="red" size="large"></v-icon>
|
|
|
+ </v-btn>
|
|
|
+
|
|
|
+ <v-btn
|
|
|
+ v-else
|
|
|
+ @click="recStop"
|
|
|
+ icon="mdi-circle"
|
|
|
+ size="large"
|
|
|
+ color="success"
|
|
|
+ >
|
|
|
+ <v-icon icon="mdi-square" size="large"></v-icon>
|
|
|
+ </v-btn>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="d-flex justify-space-between mt-3 mx-10">
|
|
|
+ <!-- 暫停按鈕 -->
|
|
|
+ <v-btn
|
|
|
+ v-if="isVideoPause"
|
|
|
+ @click="togglePause('pause')"
|
|
|
+ icon="mdi-pause"
|
|
|
+ size="large"
|
|
|
+ color="grey-darken-3"
|
|
|
+ ></v-btn>
|
|
|
+
|
|
|
+ <v-btn
|
|
|
+ v-else
|
|
|
+ @click="togglePause('play')"
|
|
|
+ icon="mdi-play"
|
|
|
+ size="large"
|
|
|
+ color="grey-darken-3"
|
|
|
+ ></v-btn>
|
|
|
+
|
|
|
+ <!-- 關閉按鈕 -->
|
|
|
+ <v-btn
|
|
|
+ @click="closeRec()"
|
|
|
+ icon="mdi-close-thick"
|
|
|
+ size="large"
|
|
|
+ color="red"
|
|
|
+ ></v-btn>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 主要廣告 -->
|
|
|
+ <v-dialog v-model="showAd">
|
|
|
+ <div class="ad-content">
|
|
|
+ <button @click="showAd = false" class="close-btn">
|
|
|
+ <svg
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ width="28"
|
|
|
+ height="28"
|
|
|
+ fill="currentColor"
|
|
|
+ class="bi bi-x-circle"
|
|
|
+ viewBox="0 0 16 16"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"
|
|
|
+ />
|
|
|
+ <path
|
|
|
+ d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <img class="cover-img" :src="ad.cover_img" alt="" />
|
|
|
+ <section>
|
|
|
+ <h2 class="title">{{ ad.title }}</h2>
|
|
|
+ <span></span>
|
|
|
+ <p v-html="ad.description"></p>
|
|
|
+
|
|
|
+ <div v-if="ad.included.length" class="product">
|
|
|
+ <p>訂單內含商品</p>
|
|
|
+ <ul>
|
|
|
+ <li v-for="(product, index) in ad.included" :key="index">
|
|
|
+ {{ product }}
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="ad.branch.length" class="branch">
|
|
|
+ <p>分店資訊</p>
|
|
|
+ <ul>
|
|
|
+ <li v-for="(name, index) in ad.branch" :key="index">
|
|
|
+ {{ name }}
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- <p class="date">{{ item.body.date }}</p> -->
|
|
|
+
|
|
|
+ <span v-if="ad.location !== ''" class="info justify-end">
|
|
|
+ <svg
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ width="20"
|
|
|
+ height="20"
|
|
|
+ viewBox="0 0 384 512"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ d="M215.7 499.2C267 435 384 279.4 384 192C384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2c12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ <p>{{ ad.location }}</p>
|
|
|
+ </span>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </v-dialog>
|
|
|
+
|
|
|
+ <!-- 隨機廣告 -->
|
|
|
+ <v-dialog v-model="secondaryAdShow">
|
|
|
+ <div class="ad-content">
|
|
|
+ <button @click="secondaryAdShow = false" class="close-btn">
|
|
|
+ <svg
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ width="28"
|
|
|
+ height="28"
|
|
|
+ fill="currentColor"
|
|
|
+ class="bi bi-x-circle"
|
|
|
+ viewBox="0 0 16 16"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"
|
|
|
+ />
|
|
|
+ <path
|
|
|
+ d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <img class="cover-img" :src="secondaryAd.img" alt="" />
|
|
|
+ <section>
|
|
|
+ <h2 class="title">{{ secondaryAd.name }}</h2>
|
|
|
+
|
|
|
+ <span class="price">
|
|
|
+ <p>{{ formatPrice(secondaryAd.price) }}</p>
|
|
|
+ <a :href="secondaryAd.url">前往官網</a>
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <p class="mb-5" v-html="secondaryAd.content"></p>
|
|
|
+
|
|
|
+ <span v-if="secondaryAd.phone !== ''" class="info">
|
|
|
+ <img
|
|
|
+ src="../assets/img/phone-solid.svg"
|
|
|
+ width="15"
|
|
|
+ alt=""
|
|
|
+ style="
|
|
|
+ filter: invert(50%) sepia(21%) saturate(1061%) hue-rotate(348deg)
|
|
|
+ brightness(96%) contrast(92%);
|
|
|
+ "
|
|
|
+ />
|
|
|
+ <p class="ms-1">{{ secondaryAd.phone }}</p>
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <span v-if="secondaryAd.address !== ''" class="info">
|
|
|
+ <svg
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ width="20"
|
|
|
+ height="20"
|
|
|
+ viewBox="0 0 384 512"
|
|
|
+ class="mt-1"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ d="M215.7 499.2C267 435 384 279.4 384 192C384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2c12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ <p>{{ secondaryAd.address }}</p>
|
|
|
+ </span>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </v-dialog>
|
|
|
+
|
|
|
+ <!-- <div
|
|
|
+ v-show="showAd"
|
|
|
+ v-for="(item, index) in ad"
|
|
|
+ :key="index"
|
|
|
+ class="ad-content"
|
|
|
+ >
|
|
|
+ <div>
|
|
|
+ <button @click="showAd = false" class="close-btn">
|
|
|
+ <svg
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ width="28"
|
|
|
+ height="28"
|
|
|
+ fill="currentColor"
|
|
|
+ class="bi bi-x-circle"
|
|
|
+ viewBox="0 0 16 16"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"
|
|
|
+ />
|
|
|
+ <path
|
|
|
+ d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ <img class="cover-img" :src="item.body.cover_img" alt="" />
|
|
|
+ <section>
|
|
|
+ <h2 class="title">{{ item.body.title }}</h2>
|
|
|
+ <span></span>
|
|
|
+ <p>
|
|
|
+ {{ item.body.description }}
|
|
|
+ </p>
|
|
|
+
|
|
|
+ <div v-if="item.body.included.length" class="product">
|
|
|
+ <p>訂單內含商品</p>
|
|
|
+ <ul>
|
|
|
+ <li v-for="(product, index) in item.body.included" :key="index">
|
|
|
+ {{ product }}
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="item.body.branch.length" class="branch">
|
|
|
+ <p>分店資訊</p>
|
|
|
+ <ul>
|
|
|
+ <li v-for="(name, index) in item.body.branch" :key="index">
|
|
|
+ {{ name }}
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <span v-if="item.body.location !== ''" class="map">
|
|
|
+ <svg
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ width="20"
|
|
|
+ height="20"
|
|
|
+ viewBox="0 0 384 512"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ d="M215.7 499.2C267 435 384 279.4 384 192C384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2c12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ <p>{{ item.body.location }}</p>
|
|
|
+ </span>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
+ </div> -->
|
|
|
</template>
|
|
|
|
|
|
-<!-- <style scoped>
|
|
|
-main {
|
|
|
- height: 150vh;
|
|
|
- min-height: -webkit-fill-available;
|
|
|
+<style lang="scss">
|
|
|
+.main-btn {
|
|
|
+ padding: 16px 70px;
|
|
|
+ font-size: 22px;
|
|
|
+ font-weight: 600;
|
|
|
+ border: none;
|
|
|
+ border-radius: 100px;
|
|
|
+ letter-spacing: 2px;
|
|
|
+ color: white;
|
|
|
+ background-color: var(--main-color);
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ &:hover {
|
|
|
+ background-color: #c19c75;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.lang-content {
|
|
|
+ width: 100%;
|
|
|
+ height: 100vh;
|
|
|
+ padding: 0 20vw;
|
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
- justify-content: center;
|
|
|
align-items: center;
|
|
|
- background-color: var(--sub-color);
|
|
|
+ justify-content: center;
|
|
|
background-color: rgba(0, 0, 0, 0.6);
|
|
|
background-blend-mode: multiply;
|
|
|
background-image: url("@/assets/img/banner.jpg");
|
|
|
background-size: cover;
|
|
|
background-position: center center;
|
|
|
+
|
|
|
+ .main-btn {
|
|
|
+ margin-bottom: 40px;
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .btn-list {
|
|
|
+ max-width: 400px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.main-containar {
|
|
|
+ width: 100%;
|
|
|
+ background-color: var(--sub-color);
|
|
|
+ // height: 150vh;
|
|
|
+ // min-height: -webkit-fill-available;
|
|
|
+ // display: flex;
|
|
|
+ // flex-direction: column;
|
|
|
+ // padding-bottom: 70vh;
|
|
|
+ // min-height: 100%;
|
|
|
+ // overflow-x: hidden;
|
|
|
+ // overflow-y: scroll;
|
|
|
+
|
|
|
+ .video-content {
|
|
|
+ width: 25vw;
|
|
|
+ position: absolute;
|
|
|
+ right: -5px;
|
|
|
+ top: -45vw;
|
|
|
+
|
|
|
+ @media (max-width: 360px) {
|
|
|
+ width: 22vw;
|
|
|
+ top: -40vw;
|
|
|
+ }
|
|
|
+
|
|
|
+ video {
|
|
|
+ // position: fixed;
|
|
|
+ // top: 50px;
|
|
|
+ // left: 0;
|
|
|
+ // right: 0;
|
|
|
+ // z-index: 10;
|
|
|
+ width: 100%;
|
|
|
+ height: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .control-btn {
|
|
|
+ width: 33px;
|
|
|
+ height: 33px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ position: absolute;
|
|
|
+ z-index: 50;
|
|
|
+ top: 70px;
|
|
|
+ right: 7px;
|
|
|
+ background: var(--main-color);
|
|
|
+ border: none;
|
|
|
+ border-radius: 100px;
|
|
|
+
|
|
|
+ img {
|
|
|
+ width: 25px;
|
|
|
+ filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(93deg)
|
|
|
+ brightness(103%) contrast(103%); // 改成白色
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-content {
|
|
|
+ width: 100%;
|
|
|
+ position: relative;
|
|
|
+ z-index: 200;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ background-color: var(--sub-color);
|
|
|
+
|
|
|
+ &.hide-menu {
|
|
|
+ min-height: 90vh;
|
|
|
+ }
|
|
|
+
|
|
|
+ .headline {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 10px 25px;
|
|
|
+ border-radius: 15px 15px 0 0;
|
|
|
+ background-color: var(--main-color);
|
|
|
+
|
|
|
+ h1 {
|
|
|
+ font-size: 1.125rem;
|
|
|
+ font-weight: 500;
|
|
|
+ color: white;
|
|
|
+
|
|
|
+ @media (max-width: 375px) {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ button {
|
|
|
+ padding-top: 3px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ border: none;
|
|
|
+ background-color: transparent;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ img {
|
|
|
+ width: 25px;
|
|
|
+ height: 20px;
|
|
|
+ transition: all 0.3s;
|
|
|
+ transform: rotate(0deg);
|
|
|
+
|
|
|
+ &.rotate {
|
|
|
+ transform: rotate(180deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-area {
|
|
|
+ // display: flex;
|
|
|
+ // flex-direction: column;
|
|
|
+ min-height: 60vh;
|
|
|
+ background: var(--sub-color);
|
|
|
+ padding: 0.5em 1em 1.5em;
|
|
|
+
|
|
|
+ .btn-list {
|
|
|
+ margin-top: 20px;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ list-style: none;
|
|
|
+
|
|
|
+ button {
|
|
|
+ margin-right: 15px;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ padding: 5px 15px;
|
|
|
+ color: white;
|
|
|
+ border: 2px solid white;
|
|
|
+ letter-spacing: 2px;
|
|
|
+ font-weight: bold;
|
|
|
+ text-shadow: 1px 1px 1px #939393;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ color: var(--sub-color);
|
|
|
+ background-color: white;
|
|
|
+ text-shadow: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ // margin-top: 20px;
|
|
|
+
|
|
|
+ .message {
|
|
|
+ max-width: 45%;
|
|
|
+ border-radius: 20px;
|
|
|
+ padding: 0.8em 1.2em;
|
|
|
+ white-space: pre-line;
|
|
|
+
|
|
|
+ &:first-child {
|
|
|
+ margin-top: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 600px) {
|
|
|
+ max-width: 75%;
|
|
|
+ font-size: 0.875em;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.message-out {
|
|
|
+ margin-left: auto;
|
|
|
+ background: var(--bg-grey);
|
|
|
+ color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.message-in {
|
|
|
+ margin-right: auto;
|
|
|
+ background: #f1f0f0;
|
|
|
+ color: black;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.message-in,
|
|
|
+ &.message-out {
|
|
|
+ margin-top: 20px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .branch {
|
|
|
+ ul,
|
|
|
+ li,
|
|
|
+ p {
|
|
|
+ margin: 10px 0;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .recommend-item {
|
|
|
+ width: 350px;
|
|
|
+ border-radius: 10px;
|
|
|
+ background-color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .recommend-item,
|
|
|
+ .ticket-item {
|
|
|
+ .cover-img {
|
|
|
+ width: 100%;
|
|
|
+ height: 22.5vh;
|
|
|
+ object-fit: contain;
|
|
|
+ overflow: hidden;
|
|
|
+ border-radius: 10px 10px 0 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ section {
|
|
|
+ padding: 15px 25px;
|
|
|
+ letter-spacing: 1px;
|
|
|
+
|
|
|
+ .title {
|
|
|
+ margin-bottom: 0;
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 1.25rem;
|
|
|
+ text-align: left;
|
|
|
+ // 超過則省略
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-line-clamp: 1;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+ line-break: after-white-space;
|
|
|
+ }
|
|
|
+
|
|
|
+ .description {
|
|
|
+ line-height: 1.8;
|
|
|
+ // text-align: justify;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .date {
|
|
|
+ color: #646464;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .link-btn {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ a {
|
|
|
+ display: block;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 200px;
|
|
|
+ margin: 25px 3px;
|
|
|
+ padding: 8px;
|
|
|
+ text-align: center;
|
|
|
+ border-radius: 20px;
|
|
|
+ color: white;
|
|
|
+ background-color: var(--main-color);
|
|
|
+ text-decoration: none;
|
|
|
+ transition: all 0.3s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .store-item {
|
|
|
+ width: 350px;
|
|
|
+
|
|
|
+ .cover-img {
|
|
|
+ position: relative;
|
|
|
+ margin-bottom: -5px;
|
|
|
+
|
|
|
+ img {
|
|
|
+ width: 100%;
|
|
|
+ height: 200px;
|
|
|
+ object-fit: cover;
|
|
|
+ }
|
|
|
+
|
|
|
+ .title {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ color: white;
|
|
|
+ font-size: 1.25rem;
|
|
|
+ letter-spacing: 2px;
|
|
|
+ text-shadow: 2px 2px 4px #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .headline {
|
|
|
+ color: white;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ section {
|
|
|
+ padding: 20px;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ border-radius: 0 0 10px 10px;
|
|
|
+ background-color: white;
|
|
|
+
|
|
|
+ .info {
|
|
|
+ margin-bottom: 15px;
|
|
|
+ p {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ img {
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-top: 3px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .ticket-item {
|
|
|
+ width: 100%;
|
|
|
+ margin-top: 20px;
|
|
|
+
|
|
|
+ h3 {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .ticket-slide {
|
|
|
+ width: 80%;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .slide-item {
|
|
|
+ max-width: 60vw;
|
|
|
+ border-radius: 10px;
|
|
|
+ background-color: white;
|
|
|
+ }
|
|
|
+
|
|
|
+ .swiper-button-prev {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .title,
|
|
|
+ .description {
|
|
|
+ // 超過則省略
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ display: -webkit-box;
|
|
|
+ // -webkit-line-clamp: 3;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+ line-break: after-white-space;
|
|
|
+ }
|
|
|
+
|
|
|
+ .title {
|
|
|
+ -webkit-line-clamp: 3;
|
|
|
+ }
|
|
|
+
|
|
|
+ .description {
|
|
|
+ -webkit-line-clamp: 5;
|
|
|
+ }
|
|
|
+
|
|
|
+ section {
|
|
|
+ margin-top: -6px;
|
|
|
+ border-radius: 0 0 10px 10px;
|
|
|
+ background-color: white;
|
|
|
+
|
|
|
+ .link-btn {
|
|
|
+ a {
|
|
|
+ max-width: 150px;
|
|
|
+ margin: 0;
|
|
|
+ padding: 8px 12px;
|
|
|
+ font-size: 0.75rem;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .price-item {
|
|
|
+ display: block;
|
|
|
+ margin-top: 10px;
|
|
|
+ text-align: end;
|
|
|
+ }
|
|
|
+
|
|
|
+ .price-info {
|
|
|
+ margin: 20px 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ @media (max-width: 575px) {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ // flex-direction: column;
|
|
|
+ // align-items: start;
|
|
|
+ }
|
|
|
+
|
|
|
+ span {
|
|
|
+ display: block;
|
|
|
+ margin: 10px 0;
|
|
|
+ padding-right: 5px;
|
|
|
+ font-size: 1rem;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--main-color);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.btn-options {
|
|
|
+ // max-width: 85%;
|
|
|
+ margin-top: 15px;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ list-style: none;
|
|
|
+
|
|
|
+ li {
|
|
|
+ margin-top: 15px;
|
|
|
+ margin-right: 20px;
|
|
|
+
|
|
|
+ button {
|
|
|
+ display: block;
|
|
|
+ width: 100%;
|
|
|
+ padding: 8px 16px;
|
|
|
+ border-radius: 5px;
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ background-color: var(--main-color);
|
|
|
+ letter-spacing: 2px;
|
|
|
+ transition: all 0.3s;
|
|
|
+ font-size: 0.875rem;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.btn-swiper {
|
|
|
+ .swiper-slide {
|
|
|
+ width: auto;
|
|
|
+ // max-width: 185px;
|
|
|
+
|
|
|
+ .btn-container {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ button {
|
|
|
+ display: block;
|
|
|
+ width: 100%;
|
|
|
+ padding: 10px 20px;
|
|
|
+ border-radius: 5px;
|
|
|
+ color: white;
|
|
|
+ border: none;
|
|
|
+ background-color: var(--main-color);
|
|
|
+ letter-spacing: 2px;
|
|
|
+ transition: all 0.3s;
|
|
|
+ font-size: 0.875rem;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.ad-content {
|
|
|
+ // width: 90%;
|
|
|
+ max-height: 95vh;
|
|
|
+ padding: 35px;
|
|
|
+ overflow-y: auto;
|
|
|
+ // position: absolute;
|
|
|
+ // z-index: 500;
|
|
|
+ border-radius: 10px;
|
|
|
+ background-color: #fff;
|
|
|
+ // box-shadow: 2px 2px 14px #5f5f5f;
|
|
|
+
|
|
|
+ .close-btn {
|
|
|
+ position: absolute;
|
|
|
+ top: 7px;
|
|
|
+ right: 5px;
|
|
|
+ border: none;
|
|
|
+ background-color: transparent;
|
|
|
+ transition: all 0.3s;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ svg {
|
|
|
+ filter: invert(57%) sepia(45%) saturate(391%) hue-rotate(350deg)
|
|
|
+ brightness(93%) contrast(84%);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .ar-link {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ section {
|
|
|
+ p {
|
|
|
+ // padding: 0 10px;
|
|
|
+ line-height: 1.7;
|
|
|
+ letter-spacing: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .title {
|
|
|
+ margin: 15px auto;
|
|
|
+ color: var(--main-color);
|
|
|
+ font-weight: 600;
|
|
|
+ font-size: 1.375em;
|
|
|
+ text-align: center;
|
|
|
+ letter-spacing: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .product,
|
|
|
+ .branch {
|
|
|
+ margin-top: 20px;
|
|
|
+ background-color: #f1e9dc;
|
|
|
+
|
|
|
+ p {
|
|
|
+ padding: 10px 0;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 0.875em;
|
|
|
+ text-align: center;
|
|
|
+ border-bottom: 2px solid #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ ul {
|
|
|
+ padding: 10px;
|
|
|
+ list-style: none;
|
|
|
+
|
|
|
+ li {
|
|
|
+ padding-bottom: 5px;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ font-weight: 500;
|
|
|
+ font-size: 0.875em;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .date {
|
|
|
+ margin: 20px 0;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: 5px;
|
|
|
+
|
|
|
+ p {
|
|
|
+ padding-left: 10px;
|
|
|
+ letter-spacing: 2px;
|
|
|
+ // font-weight: 500;
|
|
|
+ // color: var(--main-color);
|
|
|
+ }
|
|
|
+
|
|
|
+ svg {
|
|
|
+ filter: invert(57%) sepia(45%) saturate(391%) hue-rotate(350deg)
|
|
|
+ brightness(93%) contrast(84%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .cover-img {
|
|
|
+ width: 100%;
|
|
|
+ max-height: 250px;
|
|
|
+ object-fit: cover;
|
|
|
+ }
|
|
|
+
|
|
|
+ .price {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin: 10px 0;
|
|
|
+
|
|
|
+ p {
|
|
|
+ font-size: 1.25rem;
|
|
|
+ font-weight: 500;
|
|
|
+ color: var(--main-color);
|
|
|
+ }
|
|
|
+
|
|
|
+ a {
|
|
|
+ max-width: 150px;
|
|
|
+ margin: 10px 0;
|
|
|
+ padding: 8px 25px;
|
|
|
+ display: inline-block;
|
|
|
+ text-decoration: none;
|
|
|
+ text-align: center;
|
|
|
+ border-radius: 20px;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ color: white;
|
|
|
+ background-color: var(--main-color);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.slider-content {
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ img {
|
|
|
+ width: 100%;
|
|
|
+ margin-bottom: -5px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.branch {
|
|
|
+ margin-top: 30px;
|
|
|
+ ul {
|
|
|
+ padding: 0;
|
|
|
+ list-style: none;
|
|
|
+
|
|
|
+ li {
|
|
|
+ letter-spacing: 1px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.video-progress {
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ top: 40%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-content {
|
|
|
+ position: absolute;
|
|
|
+ top: 60px;
|
|
|
+ left: 0;
|
|
|
+ z-index: 100;
|
|
|
+}
|
|
|
+
|
|
|
+.vr-content {
|
|
|
+ width: 700px;
|
|
|
+ height: 700px;
|
|
|
+}
|
|
|
+
|
|
|
+.ar-link {
|
|
|
+ display: inline-block;
|
|
|
+ padding: 7px 17px;
|
|
|
+ color: #fff;
|
|
|
+ background-color: var(--main-color);
|
|
|
+ border-radius: 5px;
|
|
|
+ text-decoration: none;
|
|
|
+}
|
|
|
+
|
|
|
+.ar-card {
|
|
|
+ width: 85vw;
|
|
|
+ height: 100vh;
|
|
|
+
|
|
|
+ .v-card-title {
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ #vr-video {
|
|
|
+ height: 77vh;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.btn-swiper {
|
|
|
+ margin-top: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.v-expansion-panel-text {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ line-height: 1.7;
|
|
|
+}
|
|
|
+
|
|
|
+// 對話輸入框
|
|
|
+.chat-inputs {
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ padding: 10px 20px;
|
|
|
+ background-color: white;
|
|
|
+
|
|
|
+ input {
|
|
|
+ width: 100%;
|
|
|
+ border: none;
|
|
|
+ margin-left: 2rem;
|
|
|
+
|
|
|
+ &:focus-visible {
|
|
|
+ outline: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .menu-btn {
|
|
|
+ position: absolute;
|
|
|
+ top: 2px;
|
|
|
+ left: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ button.submit {
|
|
|
+ border: none;
|
|
|
+ background: white;
|
|
|
+ cursor: pointer;
|
|
|
+ &:hover {
|
|
|
+ img {
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ img {
|
|
|
+ padding-top: 5px;
|
|
|
+ transition: all 0.3s;
|
|
|
+ filter: invert(75%) sepia(20%) saturate(459%) hue-rotate(360deg)
|
|
|
+ brightness(100%) contrast(85%);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ::placeholder {
|
|
|
+ font-size: 1rem;
|
|
|
+ font-weight: 500;
|
|
|
+ color: var(--sub-color);
|
|
|
+ opacity: 1; /* Firefox */
|
|
|
+ }
|
|
|
+
|
|
|
+ ::-ms-input-placeholder {
|
|
|
+ /* Edge 12 -18 */
|
|
|
+ color: var(--sub-color);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 底部選單
|
|
|
+.menu {
|
|
|
+ position: fixed;
|
|
|
+ z-index: 300;
|
|
|
+ left: 0;
|
|
|
+ bottom: 0px;
|
|
|
+ right: 0;
|
|
|
+ color: var(--text-color);
|
|
|
+ background-color: white;
|
|
|
+
|
|
|
+ &.hide-menu table {
|
|
|
+ height: 0;
|
|
|
+ z-index: -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .icon {
|
|
|
+ width: 80px;
|
|
|
+
|
|
|
+ @media (max-width: 414px) {
|
|
|
+ width: 60px;
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 375px) {
|
|
|
+ width: 45px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .menu-table {
|
|
|
+ height: 100%;
|
|
|
+ transition: all 0.3s;
|
|
|
+
|
|
|
+ &.hide-table {
|
|
|
+ height: 0;
|
|
|
+
|
|
|
+ table {
|
|
|
+ display: none;
|
|
|
+ height: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ table {
|
|
|
+ width: 95%;
|
|
|
+ height: 100%;
|
|
|
+ margin: auto;
|
|
|
+ border-collapse: collapse;
|
|
|
+ position: relative;
|
|
|
+ transition: all 0.3s;
|
|
|
+
|
|
|
+ &::before,
|
|
|
+ &::after {
|
|
|
+ content: "";
|
|
|
+ display: inline-block;
|
|
|
+ width: 10px;
|
|
|
+ height: 100%;
|
|
|
+ background-color: #fff;
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0%;
|
|
|
+
|
|
|
+ @media (max-width: 390px) {
|
|
|
+ width: 8px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ left: -5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ right: -5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ tr {
|
|
|
+ td {
|
|
|
+ width: 50px;
|
|
|
+ padding: 1rem 0;
|
|
|
+ text-align: center;
|
|
|
+ border: 1px solid #ccc;
|
|
|
+
|
|
|
+ @media (max-width: 414px) {
|
|
|
+ width: 100px;
|
|
|
+ padding: 1rem 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 390px) {
|
|
|
+ padding: 0.5rem 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ padding: 0 0.3rem;
|
|
|
+ letter-spacing: 0.03rem;
|
|
|
+
|
|
|
+ @media (max-width: 430px) {
|
|
|
+ // width: 115px;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &:first-child {
|
|
|
+ td {
|
|
|
+ border-top: 0px solid transparent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ td {
|
|
|
+ border-bottom: 0px solid transparent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .question-btn {
|
|
|
+ color: var(--main-color);
|
|
|
+ font-size: 1.25rem;
|
|
|
+ font-weight: 500;
|
|
|
+ letter-spacing: 0.1rem;
|
|
|
+
|
|
|
+ @media (max-width: 414px) {
|
|
|
+ font-size: 1rem;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.anchor-voice {
|
|
|
+ height: 94vh;
|
|
|
+ color: white;
|
|
|
+ background-color: black;
|
|
|
+
|
|
|
+ .video-content {
|
|
|
+ height: 70vh;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ @media (max-width: 375px) {
|
|
|
+ height: 65vh;
|
|
|
+ }
|
|
|
+
|
|
|
+ video {
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ @media (max-width: 375px) {
|
|
|
+ margin-top: -50px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .control-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ height: 24vh;
|
|
|
+
|
|
|
+ @media (max-width: 375px) {
|
|
|
+ height: 29vh;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // .v-btn.v-btn--density-default {
|
|
|
+ // height: 55px !important;
|
|
|
+ // }
|
|
|
+}
|
|
|
+
|
|
|
+.map-img {
|
|
|
+ max-width: 100%;
|
|
|
+
|
|
|
+ &.show-anchor {
|
|
|
+ max-width: 70%;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.voice-btn {
|
|
|
+ position: fixed;
|
|
|
+ right: 5px;
|
|
|
+ top: 55px;
|
|
|
+ z-index: 100;
|
|
|
+
|
|
|
+ button {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ img {
|
|
|
+ width: 22px;
|
|
|
+ filter: invert(49%) sepia(66%) saturate(368%) hue-rotate(348deg)
|
|
|
+ brightness(91%) contrast(92%);
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ margin-left: 5px;
|
|
|
+ font-size: 0.85rem;
|
|
|
+ color: var(--main-color);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.v-btn {
|
|
|
+ // &.v-btn--density-default {
|
|
|
+ // height: auto !important;
|
|
|
+ // }
|
|
|
+
|
|
|
+ p {
|
|
|
+ font-size: 0.875rem;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.v-field__outline {
|
|
|
+ --v-field-border-width: 0 !important;
|
|
|
+}
|
|
|
+
|
|
|
+.swiper {
|
|
|
+ // width: 300px;
|
|
|
+ margin-left: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.a-modal {
|
|
|
+ position: relative !important;
|
|
|
+ top: 50% !important;
|
|
|
+ z-index: 15000 !important;
|
|
|
+}
|
|
|
+
|
|
|
+html.a-fullscreen {
|
|
|
+ position: relative !important;
|
|
|
+}
|
|
|
+
|
|
|
+a-video-controls {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 20px;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+}
|
|
|
+
|
|
|
+.fc-direction-ltr {
|
|
|
+ height: 500px;
|
|
|
+}
|
|
|
+
|
|
|
+.fc {
|
|
|
+ .fc-scrollgrid,
|
|
|
+ .fc-scrollgrid table,
|
|
|
+ .fc-daygrid-body {
|
|
|
+ height: 94%;
|
|
|
+ width: 100% !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .fc-toolbar.fc-header-toolbar {
|
|
|
+ padding: 5px;
|
|
|
+ margin-bottom: 0;
|
|
|
+ letter-spacing: 1px;
|
|
|
+ background-color: #f2f2f2;
|
|
|
+ border-radius: 5px 5px 0 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .fc-toolbar-title {
|
|
|
+ font-size: 1.125em;
|
|
|
+ }
|
|
|
+
|
|
|
+ .fc-day-past {
|
|
|
+ &.fc-day-other {
|
|
|
+ a {
|
|
|
+ text-decoration: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ a {
|
|
|
+ color: #cccccc;
|
|
|
+ text-decoration: line-through;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .fc-button .fc-icon {
|
|
|
+ color: #969696;
|
|
|
+ font-size: 1em !important;
|
|
|
+ vertical-align: baseline !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .fc-icon-chevron-left::before {
|
|
|
+ content: "<";
|
|
|
+ }
|
|
|
+
|
|
|
+ .fc-icon-chevron-right::before {
|
|
|
+ content: ">";
|
|
|
+ }
|
|
|
+
|
|
|
+ .fc-button-primary {
|
|
|
+ border: none;
|
|
|
+ background-color: transparent;
|
|
|
+
|
|
|
+ &:hover,
|
|
|
+ &:focus,
|
|
|
+ &:not(:disabled):active:focus {
|
|
|
+ box-shadow: none;
|
|
|
+ background-color: transparent;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:disabled {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .fc-daygrid-day-top {
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .fc-day-today.fc-daygrid-day,
|
|
|
+ .fc-day-future.fc-daygrid-day {
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
}
|
|
|
-</style> -->
|
|
|
+</style>
|