SyuanYu 9 月之前
父節點
當前提交
bede3c3394

+ 7 - 3
src/components/Footer.vue

@@ -1,15 +1,19 @@
 <script setup>
 import { useI18n } from "vue-i18n";
 
-const { t, locale } = useI18n();
+const { t } = useI18n();
 const props = defineProps(["url", "back"]);
 </script>
 
 <template>
   <footer>
-    <router-link :to="props.url">上一步</router-link>
+    <router-link :to="props.url">
+      {{ t("previous_step") }}
+    </router-link>
+    <a href="https://cmm.ai/101-ai-chatbot/#/">
+      {{ t("back_homepage") }}
+    </a>
     <!-- <router-link :to="props.back ? props.back : '/'">回到首頁</router-link> -->
-    <a href="https://cmm.ai/101-ai-chatbot/#/">回到首頁</a>
   </footer>
 </template>
 

+ 3 - 0
src/components/Language.vue

@@ -13,6 +13,9 @@ onMounted(() => {
 
   if (lang && lang !== "") {
     chooseLang(lang);
+  } else {
+    let localStorageLang = localStorage.getItem("lang");
+    chooseLang(localStorageLang);
   }
 });
 

+ 67 - 24
src/language/en.json

@@ -1,4 +1,41 @@
 {
+  "previous_step": "Previous Step",
+  "next_step": "Next Step",
+  "back_homepage": "Back to the Homepage",
+  "confirm": "Confirm",
+
+  "postcard": {
+    "step1": {
+      "text_1": "AI postcard can recognize travelers from different nationalities.",
+      "text_2": "Just need to take a selfie with your phone, then the AI technology",
+      "text_3": "can perfectly blend yourself into various scenic spots in Taiwan",
+      "text_4": "If you can't visit every place on this trip,",
+      "text_5": "Then this is our gift for you",
+      "text_6": "Know more about Taiwan",
+      "text_7": "Get the photo instantly"
+    },
+
+    "step2": {
+      "text_1": "Find a clean white board",
+      "text_2": "Choose the background ",
+      "text_3": "Select from over 20 scenic spot photos",
+      "text_4": "Capture photo with either front or back camera of your phone",
+      "text_5": "Get the photo!",
+      "text_6": "Share to friends"
+    },
+
+    "step5": {
+      "text_1": "The postcard is being created...",
+      "text_2": "Please wait for approximately 30 seconds",
+      "text_3": "AI is quickly taking you to the destination.",
+      "text_4": "Please upload your photo",
+      "text_5": "Choose the file",
+      "text_6": "Reset",
+      "text_7": "Crop",
+      "text_8": "Upload again"
+    }
+  },
+
   "taipei_yangmingshan": "Taipei Yangmingshan Mother's Day",
   "taipei_yangmingshan_description": "",
 
@@ -76,32 +113,38 @@
 
   "skylantern": {
     "home": {
-      "text_1": "在這裡",
-      "text_2": "選一顆天燈",
-      "text_3": "寫下你的祝福或願望吧",
-      "text_4": "您傳遞出去的願望",
-      "text_5": "會在 101 觀景台 5F 購票處的",
-      "text_6": "環景螢幕牆旁的螢幕定時播放"
+      "text_1": "Here",
+      "text_2": "Choose a sky lantern",
+      "text_3": "Write down your blessings or wishes",
+      "text_4": "Your wishes",
+      "text_5": "will be displayed on the panoramic screen next to the ticket booth on the 5th floor of Taipei 101 Observatory"
     },
     "step1": {
-      "text_1": "天燈的起源",
-      "text_2": "「天燈」起初是為了傳遞訊息之用,但目前通常則被當成節慶折福許願的工具,象徵收穫的成功和幸福每一年。許多旅遊景點每天晚上都提供放天燈的活動",
-      "text_3": "祈福目的",
-      "text_4": "健康|平安",
-      "text_5": "幸福|快樂",
-      "text_6": "前途|事業",
-      "text_7": "愛情|婚姻",
-      "text_8": "金錢|發財",
-      "text_9": "考試|讀書",
-      "text_10": "環保天燈",
-      "text_11": "天燈是鐵絲或竹子,紙、油紙組成。當天燈的燃料燒完後,會掉落附近地面,餘火會燒到附近住家的屋頂或菜園、甚至引發森林大火,並且材料會殘留重金屬,可能對生態鏈造成嚴重危害。於是2018年起台灣研發出了能在空中徹底燃燒成灰的",
-      "text_12": "[全紙環保天燈]",
-      "text_13": "當然,在101放天燈,也非常環保"
+      "text_1": "The origin of sky lanterns",
+      "text_2": "Originally used for message transmission, sky lanterns have now become a symbol of festival blessings and wishes, representing success and happiness. Many tourist destinations offer sky lantern releasing activities every evening.",
+      "text_3": "Purpose of blessings",
+      "text_4": "Health|Safety",
+      "text_5": "Happiness|Joy",
+      "text_6": "Prospects|Career",
+      "text_7": "Love|Marriage",
+      "text_8": "Money|Wealth",
+      "text_9": "Exams|Study",
+      "text_10": "Environment-Friendly Sky Lantern",
+      "text_11": "The lantern is made of wire or bamboo, paper and oil paper. After the fuel inside the lantern burns out, it falls to the ground nearby, and the remaining fire may spread to nearby homes, gardens, or even ignite forest fires. Additionally, the materials may leave behind heavy metal residues, posing serious harm to the ecosystem. Therefore, in 2018, Taiwan developed an environment-friendly alternative that completely burns into ash while in the air",
+      "text_12": "[All-Paper Eco-Friendly Sky Lantern]",
+      "text_13": "Of course, releasing a lantern in Taipei 101 is also pretty eco-friendly"
     },
-    "wish": "寫下你的願望",
-    "preview": "預覽",
-    "submit": "送出",
-    "success": "送出成功",
-    "received": "已經收到您的天燈"
+
+    "step2": {
+      "text_1": "Write down your wishes",
+      "text_2": "Preview",
+      "text_3": "Send"
+    },
+
+    "step3": {
+      "text_1": "Sent Successfully!",
+      "text_2": "Received your lantern",
+      "text_3": "Send"
+    }
   }
 }

+ 50 - 7
src/language/ja.json

@@ -1,4 +1,41 @@
 {
+  "previous_step": "戻る",
+  "next_step": "続く",
+  "back_homepage": "トップページへ戻る",
+  "confirm": "決定",
+
+  "postcard": {
+    "step1": {
+      "text_1": "AIポストカードはは様々の国籍の旅行者を識別できます。",
+      "text_2": "スマートフォンで写真を撮り、AIの技術を利用すれば",
+      "text_3": "台湾の各観光スポットに旅行したような感じに加工できます。",
+      "text_4": "もし今回の台湾旅行はすべてスポットに訪ねることができなかったら、",
+      "text_5": "これは私たちのプレゼントです。",
+      "text_6": "台湾を知ろう",
+      "text_7": "写真をゲット"
+    },
+
+    "step2": {
+      "text_1": "真っ白の壁を選ぶ",
+      "text_2": "背景を選ぶ",
+      "text_3": "20種類以上のスポット写真の中を選ぶ",
+      "text_4": "カメラ機能を起動し",
+      "text_5": "写真をゲット!",
+      "text_6": "友達にシェアしましょう。"
+    },
+
+    "step5": {
+      "text_1": "ポストカードを作成中…",
+      "text_2": "30秒ほど待つ",
+      "text_3": "AIが迅速に目的地へご案内します。",
+      "text_4": "写真をアップロード",
+      "text_5": "ファイルを選ぶ",
+      "text_6": "リセット",
+      "text_7": "切り取り",
+      "text_8": "再アップロード"
+    }
+  },
+
   "taipei_yangmingshan": "陽明山国家公園ー母の日",
   "taipei_yangmingshan_description": "",
 
@@ -80,8 +117,7 @@
       "text_2": "ランタン(天燈)を選んで、",
       "text_3": "祈りか願い事を書きましょう。",
       "text_4": "書いた願い事は",
-      "text_5": "台北101の展望台5階にあるチケット売り場の",
-      "text_6": "液晶ディスプレイに放映されます。"
+      "text_5": "台北101の展望台5階にあるチケット売り場の液晶ディスプレイに放映されます。"
     },
     "step1": {
       "text_1": "ランタンの由来",
@@ -98,10 +134,17 @@
       "text_12": "[紙スカイランタン]を開発しました。",
       "text_13": "もちろん、台北101でランタンを飛ばすのも環境に優しいです。"
     },
-    "wish": "願い事を書いてください。",
-    "preview": "プレビュー",
-    "submit": "送信",
-    "success": "送信完了!",
-    "received": "書いたランタンを届きました。"
+
+    "step2": {
+      "text_1": "願い事を書いてください。",
+      "text_2": "プレビュー",
+      "text_3": "送信"
+    },
+
+    "step3": {
+      "text_1": "送信完了!",
+      "text_2": "書いたランタンを届きました。",
+      "text_3": "送信"
+    }
   }
 }

+ 50 - 7
src/language/ko.json

@@ -1,4 +1,41 @@
 {
+  "previous_step": "上一步",
+  "next_step": "下一步",
+  "back_homepage": "回到首頁",
+  "confirm": "確定",
+
+  "postcard": {
+    "step1": {
+      "text_1": "AI 明信片可以分辨來自不同國籍的旅客",
+      "text_2": "只需要使用手機自拍,加上 AI 技術",
+      "text_3": "將您完美合成至台灣各景點",
+      "text_4": "如果您此趟旅程沒辦法每個地方都去過",
+      "text_5": "那這是我們送給您的禮物",
+      "text_6": "認識台灣",
+      "text_7": "馬上取得照片"
+    },
+
+    "step2": {
+      "text_1": "尋找一面乾淨的白牆",
+      "text_2": "選擇背景",
+      "text_3": "20 款以上景點照片挑選",
+      "text_4": "手機前後鏡頭拍攝",
+      "text_5": "獲得照片!",
+      "text_6": "馬上分享給朋友"
+    },
+
+    "step5": {
+      "text_1": "明信片製作中…",
+      "text_2": "請稍等約 30 秒",
+      "text_3": "AI 正在迅速地帶您前往該景點",
+      "text_4": "請上傳您的相片",
+      "text_5": "照相/選擇相片",
+      "text_6": "重置",
+      "text_7": "裁剪",
+      "text_8": "重新上傳"
+    }
+  },
+
   "taipei_yangmingshan": "臺北陽明山-母親節",
   "taipei_yangmingshan_description": "",
 
@@ -80,8 +117,7 @@
       "text_2": "選一顆天燈",
       "text_3": "寫下你的祝福或願望吧",
       "text_4": "您傳遞出去的願望",
-      "text_5": "會在 101 觀景台 5F 購票處的",
-      "text_6": "環景螢幕牆旁的螢幕定時播放"
+      "text_5": "會在 101 觀景台 5F 購票處的環景螢幕牆旁的螢幕定時播放"
     },
     "step1": {
       "text_1": "天燈的起源",
@@ -98,10 +134,17 @@
       "text_12": "[全紙環保天燈]",
       "text_13": "當然,在101放天燈,也非常環保"
     },
-    "wish": "寫下你的願望",
-    "preview": "預覽",
-    "submit": "送出",
-    "success": "送出成功",
-    "received": "已經收到您的天燈"
+
+    "step2": {
+      "text_1": "寫下你的願望",
+      "text_2": "預覽",
+      "text_3": "送出"
+    },
+
+    "step3": {
+      "text_1": "送出成功!",
+      "text_2": "已經收到您的天燈",
+      "text_3": "送出"
+    }
   }
 }

+ 51 - 7
src/language/zh.json

@@ -1,4 +1,41 @@
 {
+  "previous_step": "上一步",
+  "next_step": "下一步",
+  "back_homepage": "回到首頁",
+  "confirm": "確定",
+
+  "postcard": {
+    "step1": {
+      "text_1": "AI 明信片可以分辨來自不同國籍的旅客",
+      "text_2": "只需要使用手機自拍,加上 AI 技術",
+      "text_3": "將您完美合成至台灣各景點",
+      "text_4": "如果您此趟旅程沒辦法每個地方都去過",
+      "text_5": "那這是我們送給您的禮物",
+      "text_6": "認識台灣",
+      "text_7": "馬上取得照片"
+    },
+
+    "step2": {
+      "text_1": "尋找一面乾淨的白牆",
+      "text_2": "選擇背景",
+      "text_3": "20 款以上景點照片挑選",
+      "text_4": "手機前後鏡頭拍攝",
+      "text_5": "獲得照片!",
+      "text_6": "馬上分享給朋友"
+    },
+
+    "step5": {
+      "text_1": "明信片製作中…",
+      "text_2": "請稍等約 30 秒",
+      "text_3": "AI 正在迅速地帶您前往該景點",
+      "text_4": "請上傳您的相片",
+      "text_5": "照相/選擇相片",
+      "text_6": "重置",
+      "text_7": "裁剪",
+      "text_8": "重新上傳"
+    }
+  },
+
   "taipei_yangmingshan": "臺北陽明山-母親節",
   "taipei_yangmingshan_description": "",
 
@@ -80,9 +117,9 @@
       "text_2": "選一顆天燈",
       "text_3": "寫下你的祝福或願望吧",
       "text_4": "您傳遞出去的願望",
-      "text_5": "會在 101 觀景台 5F 購票處的",
-      "text_6": "環景螢幕牆旁的螢幕定時播放"
+      "text_5": "會在 101 觀景台 5F 購票處的環景螢幕牆旁的螢幕定時播放"
     },
+    
     "step1": {
       "text_1": "天燈的起源",
       "text_2": "「天燈」起初是為了傳遞訊息之用,但目前通常則被當成節慶折福許願的工具,象徵收穫的成功和幸福每一年。許多旅遊景點每天晚上都提供放天燈的活動",
@@ -98,10 +135,17 @@
       "text_12": "[全紙環保天燈]",
       "text_13": "當然,在101放天燈,也非常環保"
     },
-    "wish": "寫下你的願望",
-    "preview": "預覽",
-    "submit": "送出",
-    "success": "送出成功",
-    "received": "已經收到您的天燈"
+
+    "step2": {
+      "text_1": "寫下你的願望",
+      "text_2": "預覽",
+      "text_3": "送出"
+    },
+
+    "step3": {
+      "text_1": "送出成功!",
+      "text_2": "已經收到您的天燈",
+      "text_3": "送出"
+    }
   }
 }

+ 1 - 1
src/views/HomeView.vue

@@ -70,7 +70,7 @@ p {
   }
 
   @media (max-width: 600px) {
-    font-size: 1.25rem;
+    font-size: 1rem;
   }
 }
 

+ 2 - 3
src/views/Skylantern.vue

@@ -15,12 +15,11 @@ const { t, locale } = useI18n();
 
     <p>
       {{ t("skylantern.home.text_4") }} <br />
-      {{ t("skylantern.home.text_5") }} <br />
-      {{ t("skylantern.home.text_6") }}
+      {{ t("skylantern.home.text_5") }}
     </p>
 
     <router-link to="/skylantern_step1" class="main-btn mt-15">
-      下一步
+      {{ t("next_step") }}
     </router-link>
   </div>
 

+ 1 - 1
src/views/SkylanternStep_1.vue

@@ -47,7 +47,7 @@ const { t } = useI18n();
     </section>
 
     <router-link to="/skylantern_step2" class="main-btn mt-10">
-      下一步
+      {{ t("next_step") }}
     </router-link>
 
     <Footer url="/skylantern" back="/skylanternhome" />

+ 9 - 3
src/views/SkylanternStep_2.vue

@@ -1,9 +1,11 @@
 <script setup>
 import { ref } from "vue";
+import { useI18n } from "vue-i18n";
 import { useRouter } from "vue-router";
 import axios from "axios";
 import Footer from "../components/Footer.vue";
 
+const { t } = useI18n();
 let router = useRouter();
 let loading = ref(false);
 let inputValue = ref("");
@@ -57,12 +59,16 @@ function getValue(state) {
       />
     </div>
 
-    <h3 class="title">寫下你的願望</h3>
+    <h3 class="title">{{ t("skylantern.step2.text_1") }}</h3>
     <input v-model="inputValue" type="text" />
 
     <div class="d-flex flex-column align-center my-15">
-      <button @click="getValue(true)" class="main-btn mb-5">預覽</button>
-      <button @click="getValue(false)" class="main-btn mb-5">送出</button>
+      <button @click="getValue(true)" class="main-btn mb-5">
+        {{ t("skylantern.step2.text_2") }}
+      </button>
+      <button @click="getValue(false)" class="main-btn mb-5">
+        {{ t("skylantern.step2.text_3") }}
+      </button>
     </div>
 
     <Footer url="/skylantern_step1" back="/skylanternhome" />

+ 7 - 2
src/views/SkylanternStep_3.vue

@@ -1,15 +1,20 @@
 <script setup>
+import { useI18n } from "vue-i18n";
 import Footer from "../components/Footer.vue";
+
+const { t } = useI18n();
 </script>
 
 <template>
   <div class="lartern-content">
-    <h3 class="title">送出成功!</h3>
+    <h3 class="title">
+      {{ t("skylantern.step3.text_1") }}
+    </h3>
 
     <img width="100" src="../assets/img/confirm.png" alt="" />
 
     <p class="mt-5 px-5">
-      已經收到您的天燈 <br />
+      {{ t("skylantern.step3.text_2") }} <br />
       天燈將於整點在 101 五樓觀景台售票處旁的環景螢幕進行播放,敬請期待。
     </p>
   </div>

+ 14 - 10
src/views/Step_1.vue

@@ -1,30 +1,34 @@
 <script setup>
-import { ref, reactive } from "vue";
+import { useI18n } from "vue-i18n";
 import "animate.css";
-import axios from "axios";
 import Marquee from "../components/Marquee.vue";
+
+const { t } = useI18n();
 </script>
 
 <template>
   <div class="content">
     <section class="px-3 mb-10">
       <p>
-        AI 明信片可以分辨來自不同國籍的旅客 <br />
-        只需要使用手機自拍,加上 AI 技術 <br />
-        將您完美合成至台灣各景點
+        {{ t("postcard.step1.text_1") }} <br />
+        {{ t("postcard.step1.text_2") }} <br />
+        {{ t("postcard.step1.text_3") }}
       </p>
       <p>
-        如果您此趟旅程沒辦法每個地方都去過 <br />
-        那這是我們送給您的禮物
+        {{ t("postcard.step1.text_4") }} <br />
+        {{ t("postcard.step1.text_5") }}
       </p>
     </section>
 
     <Marquee />
 
-    <router-link to="/step2" class="main-btn">下一步</router-link>
+    <router-link to="/step2" class="main-btn">
+      {{ t("next_step") }}
+    </router-link>
+    
     <div class="hashtag">
-      <span># 認識台灣</span>
-      <span># 馬上取得照片</span>
+      <span># {{ t("postcard.step1.text_6") }}</span>
+      <span># {{ t("postcard.step1.text_7") }}</span>
     </div>
   </div>
 </template>

+ 13 - 9
src/views/Step_2.vue

@@ -1,6 +1,9 @@
 <script setup>
+import { useI18n } from "vue-i18n";
 import "animate.css";
 import Footer from "../components/Footer.vue";
+
+const { t } = useI18n();
 </script>
 
 <template>
@@ -8,7 +11,7 @@ import Footer from "../components/Footer.vue";
     <ul class="step-list">
       <li>
         <img class="num" src="../assets/img/step-1.png" alt="" />
-        <p>尋找一面乾淨的白牆</p>
+        <p>{{ t("postcard.step2.text_1") }}</p>
         <img class="arrow" src="../assets/img/arrow_b.png" alt="" />
       </li>
 
@@ -19,8 +22,8 @@ import Footer from "../components/Footer.vue";
         </div>
         <div class="step">
           <img class="num" src="../assets/img/step-2.png" alt="" />
-          <p>選擇背景</p>
-          <small>20 款以上景點照片挑選</small>
+          <p>{{ t("postcard.step2.text_2") }}</p>
+          <small>{{ t("postcard.step2.text_3") }}</small>
         </div>
       </li>
 
@@ -31,8 +34,8 @@ import Footer from "../components/Footer.vue";
         </div>
         <div class="step">
           <img class="num" src="../assets/img/step-3.png" alt="" />
-          <p>手機前後鏡頭拍攝</p>
-          <small>人臉對準綠色框框</small>
+          <p>{{ t("postcard.step2.text_4") }}</p>
+          <!-- <small>人臉對準綠色框框</small> -->
         </div>
       </li>
 
@@ -42,13 +45,15 @@ import Footer from "../components/Footer.vue";
         </div>
         <div class="step">
           <img class="num" src="../assets/img/step-4.png" alt="" />
-          <p>獲得照片!</p>
-          <small>馬上分享給朋友</small>
+          <p>{{ t("postcard.step2.text_5") }}</p>
+          <small>{{ t("postcard.step2.text_6") }}</small>
         </div>
       </li>
     </ul>
 
-    <router-link to="/step3" class="main-btn">下一步</router-link>
+    <router-link to="/step3" class="main-btn">
+      {{ t("next_step") }}
+    </router-link>
 
     <Footer url="/step1" />
   </div>
@@ -138,7 +143,6 @@ span {
 }
 
 .content {
-
   p {
     &:first-child {
       margin-bottom: 1.625rem;

+ 6 - 22
src/views/Step_3.vue

@@ -9,16 +9,12 @@ import Footer from "../components/Footer.vue";
 
 const router = useRouter();
 const store = useMainStore();
-const { t, locale } = useI18n();
+const { t } = useI18n();
 
 const apiUrl = import.meta.env.VITE_API_URL;
 const imgUrl = import.meta.env.VITE_API_IMG_URL;
 console.log("VITE_API_URL", apiUrl);
 
-// let bgImg = reactive({
-//   list: [],
-// });
-
 let assignBgImg = ref("");
 
 function handleBgImg(item) {
@@ -35,21 +31,7 @@ function handleBgImg(item) {
   console.log("store.styleNum", store.styleNum);
 }
 
-// async function getBgImgNames() {
-//   let url = `${apiUrl}/sd/bg_img_names`;
-
-//   try {
-//     let response = await axios.get(url);
-//     console.log("getBgImgNames", response.data);
-//     response.data.map((item) => bgImg.list.push(item));
-//     console.log("bgImg.list", bgImg.list);
-//   } catch (error) {
-//     console.log("error", error);
-//   }
-// }
-
 onMounted(() => {
-  // getBgImgNames();
   getParameters();
 });
 
@@ -334,7 +316,7 @@ function checkImg() {
 
 <template>
   <div class="content">
-    <p class="title">選擇背景</p>
+    <p class="title">{{ t("postcard.step2.text_2") }}</p>
     <div class="img-content">
       <div class="slider-btn">
         <button class="prev" @click="prev">
@@ -373,7 +355,7 @@ function checkImg() {
           :src="`http://172.104.93.163:3219/static/assets/img/bg/${item.bg_img}`"
           alt=""
         /> -->
-       
+
         <!-- <p>{{ item.bg_img.replace(".png", "") }}</p> -->
 
         <img
@@ -463,7 +445,9 @@ function checkImg() {
         </div>
       </div> -->
 
-      <a @click="checkImg()" href="javascript:;" class="main-btn">下一步</a>
+      <a @click="checkImg()" href="javascript:;" class="main-btn">
+        {{ t("next_step") }}
+      </a>
 
       <!-- <router-link to="/step5" class="main-btn">下一步</router-link> -->
 

+ 4 - 69
src/views/Step_4.vue

@@ -1,73 +1,11 @@
 <script setup>
-import { ref, reactive } from "vue";
 import { useMainStore } from "@/stores/store";
-import { useRouter } from "vue-router";
 import { useI18n } from "vue-i18n";
-import axios from "axios";
 import Footer from "../components/Footer.vue";
 
 const { t } = useI18n();
-const router = useRouter();
 const store = useMainStore();
-const apiUrl = import.meta.env.VITE_API_URL;
-const imgUrl = import.meta.env.VITE_API_IMG_URL;
-
 console.log("step5 store.assignBgImg", store.assignBgImg);
-
-let fileInput = ref(null);
-let imgFile = ref(null);
-
-// 選擇檔案
-function onFileChange() {
-  console.log("fileInput", fileInput.value);
-  if (fileInput.value.files.length) {
-    imgFile.value = fileInput.value.files[0];
-  }
-  console.log("imgFile.value", imgFile.value);
-}
-
-let imgLoading = ref(false);
-
-// 算圖欄位
-let runParameters = reactive({
-  seed: "54987890",
-  denoising_strength: "0.35",
-  batch_size: "1",
-  n_iter: "30",
-});
-
-// 算圖
-async function upload() {
-  console.log("upload");
-  if (!imgFile.value) {
-    return;
-  }
-
-  store.imgPath = "";
-  imgLoading.value = true;
-  console.log("store styleNum >>>", store.styleNum);
-
-  let url = `${apiUrl}/sd/run?seed=${runParameters.seed}&denoising_strength=${runParameters.denoising_strength}&batch_size=${runParameters.batch_size}&n_iter=${runParameters.n_iter}&style_num=${store.styleNum}`;
-
-  // 人物圖
-  const formData = new FormData();
-  formData.append("file", imgFile.value);
-
-  try {
-    let response = await axios.post(url, formData);
-    console.log("runImg", response);
-
-    if (response.status === 200) {
-      store.imgPath = response.data[0].path;
-      imgLoading.value = false;
-      console.log("store.imgPath", store.imgPath);
-
-      router.push("/step6");
-    }
-  } catch (error) {
-    console.log("error", error);
-  }
-}
 </script>
 
 <template>
@@ -79,7 +17,7 @@ async function upload() {
         <v-img
           max-width="500"
           cover
-          class="cover ma-5"
+          class="my-5 mx-auto"
           :lazy-src="`http://172.104.93.163:3219/static/assets/img/bg/${store.assignBgImg.bg_img}`"
           :src="`http://172.104.93.163:3219/static/assets/img/bg/${store.assignBgImg.bg_img}`"
         >
@@ -93,11 +31,6 @@ async function upload() {
           </template>
         </v-img>
 
-        <!-- <img
-          class="cover mb-5"
-          :src="`http://172.104.93.163:3219/static/assets/img/bg/${store.assignBgImg.bg_img}`"
-          alt=""
-        /> -->
         <p>{{ t(store.assignBgImg.title) }}</p>
 
         <p
@@ -106,7 +39,9 @@ async function upload() {
         ></p>
       </div>
 
-      <router-link to="/step5" class="main-btn mt-auto">確定</router-link>
+      <router-link to="/step5" class="main-btn mt-auto">
+        {{ t("confirm") }}
+      </router-link>
     </v-container>
 
     <Footer url="/step3" />

+ 46 - 40
src/views/Step_5.vue

@@ -2,10 +2,12 @@
 import { ref, reactive, onMounted } from "vue";
 import { useMainStore } from "@/stores/store";
 import { useRouter } from "vue-router";
+import { useI18n } from "vue-i18n";
 import axios from "axios";
 import Footer from "../components/Footer.vue";
 import VuePictureCropper, { cropper } from "vue-picture-cropper";
 
+const { t } = useI18n();
 const router = useRouter();
 const store = useMainStore();
 const apiUrl = import.meta.env.VITE_API_URL;
@@ -76,33 +78,37 @@ function reset() {
   cropper.reset();
 }
 
-// function ready() {
-//   console.log("Cropper is ready.");
-// }
-// 測試結束
+// 重新上傳
+function remove() {
+  file.value = null;
+  imgFile.value = null;
+  // imageUrl.value = null;
+  pic.value = "";
+  result.dataURL = "";
+}
 
 console.log("step5 store.assignBgImg", store.assignBgImg);
 
 let file = ref(null);
-let fileInput = ref(null);
+// let fileInput = ref(null);
 let imgFile = ref(null);
-let imageUrl = ref(null);
+// let imageUrl = ref(null);
 
 // 選擇檔案
-function onFileChange() {
-  console.log("fileInput", fileInput.value);
-  if (fileInput.value.files.length) {
-    imgFile.value = fileInput.value.files[0];
-
-    // 預覽相片
-    const reader = new FileReader();
-    reader.onload = () => {
-      imageUrl.value = reader.result;
-    };
-    reader.readAsDataURL(imgFile.value);
-  }
-  console.log("imgFile.value", imgFile.value);
-}
+// function onFileChange() {
+//   console.log("fileInput", fileInput.value);
+//   if (fileInput.value.files.length) {
+//     imgFile.value = fileInput.value.files[0];
+
+//     // 預覽相片
+//     const reader = new FileReader();
+//     reader.onload = () => {
+//       imageUrl.value = reader.result;
+//     };
+//     reader.readAsDataURL(imgFile.value);
+//   }
+//   console.log("imgFile.value", imgFile.value);
+// }
 
 let imgLoading = ref(false);
 
@@ -123,7 +129,7 @@ async function upload() {
 
   store.imgPath = "";
   imgLoading.value = true;
-  console.log("store styleNum >>>", store.styleNum);
+  console.log("store styleNum", store.styleNum);
 
   let url = `${apiUrl}/sd/run?seed=${runParameters.seed}&denoising_strength=${runParameters.denoising_strength}&batch_size=${runParameters.batch_size}&n_iter=${runParameters.n_iter}&style_num=${store.styleNum}`;
 
@@ -147,12 +153,6 @@ async function upload() {
   }
 }
 
-function remove() {
-  file.value = null;
-  imgFile.value = null;
-  imageUrl.value = null;
-}
-
 const openUploadInput = () => {
   if (uploadInput.value) {
     uploadInput.value.click();
@@ -168,8 +168,8 @@ const openUploadInput = () => {
         class="d-flex flex-column align-center justify-center"
       >
         <p class="mb-15">
-          明信片製作中… <br />
-          請稍等約 30 秒
+          {{ t("postcard.step5.text_1") }}<br />
+          {{ t("postcard.step5.text_2") }}
         </p>
 
         <v-progress-circular
@@ -179,11 +179,11 @@ const openUploadInput = () => {
           indeterminate
         ></v-progress-circular>
 
-        <p class="mt-15">AI 正在迅速地帶您前往該景點</p>
+        <p class="mt-15">{{ t("postcard.step5.text_3") }}</p>
       </div>
 
       <div v-else>
-        <p class="title mb-5">請上傳您的相片</p>
+        <p class="title mb-5">{{ t("postcard.step5.text_4") }}</p>
 
         <div class="d-flex justify-center">
           <v-btn
@@ -194,7 +194,7 @@ const openUploadInput = () => {
           >
             <span class="d-flex align-center">
               <v-icon icon="mdi-camera" class="me-3 pt-1"> </v-icon>
-              <p>照相/選擇相片</p>
+              <p>{{ t("postcard.step5.text_5") }}</p>
             </span>
           </v-btn>
         </div>
@@ -232,11 +232,11 @@ const openUploadInput = () => {
 
           <div class="mt-5 d-flex justify-end">
             <v-btn @click="reset" color="grey" variant="flat" class="me-3">
-              重置
+              {{ t("postcard.step5.text_6") }}
             </v-btn>
 
             <v-btn @click="getResult" color="primary" variant="flat">
-              裁剪
+              {{ t("postcard.step5.text_7") }}
             </v-btn>
           </div>
         </div>
@@ -254,6 +254,10 @@ const openUploadInput = () => {
             ></div>
             <!-- <img :src="result.dataURL" /> -->
           </div>
+
+          <p class="text-white mt-5">
+            將人像放置於畫面右下角<br />會得到最好看的畫面唷!
+          </p>
         </section>
 
         <!-- <v-file-input
@@ -269,14 +273,16 @@ const openUploadInput = () => {
         <div class="preview-img">
           <img class="w-100 mt-5" :src="imageUrl" alt="照片" v-if="imageUrl" />
         </div> -->
+
+        <div class="btn-content">
+          <button @click="remove()" class="main-btn">
+            {{ t("postcard.step5.text_8") }}
+          </button>
+          <button @click="upload()" class="main-btn">{{ t("confirm") }}</button>
+        </div>
       </div>
 
       <!-- <router-link to="/step6" class="main-btn">確定</router-link> -->
-
-      <div class="btn-content">
-        <button @click="remove()" class="main-btn">重新上傳</button>
-        <button @click="upload()" class="main-btn">確定</button>
-      </div>
     </v-container>
 
     <Footer url="/step4" />
@@ -346,7 +352,7 @@ const openUploadInput = () => {
 
 .btn-content {
   width: 100%;
-  padding: 100px 20px 20px;
+  padding: 100px 10px 20px;
   display: flex;
   justify-content: center;
   // position: absolute;

+ 42 - 7
src/views/Step_6.vue

@@ -1,14 +1,13 @@
 <script setup>
+import { ref } from "vue";
 import { useI18n } from "vue-i18n";
 import { useMainStore } from "@/stores/store";
+import axios from "axios";
 import Footer from "../components/Footer.vue";
 
 const { t } = useI18n();
 const store = useMainStore();
 
-// const apiUrl = import.meta.env.VITE_API_URL;
-// const imgUrl = import.meta.env.VITE_API_IMG_URL;
-
 // 使用者點擊分享時帶入的資訊
 const shareData = {
   url: store.imgPath, // 要分享的 URL
@@ -18,6 +17,34 @@ const shareData = {
 
 console.log("shareData", shareData);
 
+const imageUrl = ref("");
+
+// 儲存圖片
+const downloadImage = async (url) => {
+  try {
+    const response = await axios({
+      url: url,
+      method: "GET",
+      responseType: "blob",
+    });
+
+    imageUrl.value = URL.createObjectURL(new Blob([response.data]));
+
+    saveImage();
+  } catch (error) {
+    console.error("Error downloading the image:", error);
+  }
+};
+
+const saveImage = () => {
+  const a = document.createElement("a");
+  a.href = imageUrl.value;
+  a.download = "101-AI-Postcard.jpg"; // 檔案名稱
+  document.body.appendChild(a);
+  a.click();
+  document.body.removeChild(a);
+};
+
 async function share() {
   try {
     // 使用 Web Share API
@@ -53,8 +80,14 @@ async function share() {
 <template>
   <div class="content main-bg">
     <v-container class="px-5 px-sm-15 d-flex flex-column align-center">
-      <div class="mb-10 img-item">
-        <img class="w-100" :src="store.imgPath" alt="" />
+      <div id="result" class="mb-10 img-item">
+        <img
+          id="imageElement"
+          ref="imageElement"
+          class="w-100"
+          :src="store.imgPath"
+          alt=""
+        />
         <p>{{ t(store.assignBgImg.title) }}</p>
       </div>
 
@@ -62,8 +95,10 @@ async function share() {
         class="text-start px-5 description"
         v-html="t(store.assignBgImg.description)"
       ></p>
-      <!-- {{ t(`${store.assignBgImg.title}_description`) }} -->
-      <button @click="share()" class="main-btn mt-15">分享相片</button>
+      <!-- <button @click="share()" class="main-btn mt-15">分享相片</button> -->
+      <button @click="downloadImage(store.imgPath)" class="main-btn mt-15">
+        儲存相片
+      </button>
     </v-container>
     <Footer url="/step5" />
   </div>