|
@@ -1,130 +1,382 @@
|
|
|
<script setup>
|
|
|
-import { ref } from "vue";
|
|
|
-import { useI18n } from "vue-i18n";
|
|
|
+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;
|
|
|
+const imgUrl = import.meta.env.VITE_API_IMG_URL;
|
|
|
|
|
|
-// 使用者點擊分享時帶入的資訊
|
|
|
-const shareData = {
|
|
|
- url: store.imgPath, // 要分享的 URL
|
|
|
- title: "101", // 標題
|
|
|
- text: "AI明信片", // 文字內容
|
|
|
-};
|
|
|
+// 測試開始
|
|
|
+const isShowModal = ref(false);
|
|
|
+const uploadInput = ref(null);
|
|
|
+const pic = ref("");
|
|
|
+const result = reactive({
|
|
|
+ dataURL: "",
|
|
|
+ // blobURL: "",
|
|
|
+});
|
|
|
|
|
|
-console.log("shareData", shareData);
|
|
|
+function selectFile(e) {
|
|
|
+ pic.value = "";
|
|
|
+ result.dataURL = "";
|
|
|
+ // result.blobURL = "";
|
|
|
|
|
|
-const imageUrl = ref("");
|
|
|
+ // Get selected files
|
|
|
+ const { files } = e.target;
|
|
|
+ if (!files || !files.length) return;
|
|
|
|
|
|
-// 儲存圖片
|
|
|
-const downloadImage = async (url) => {
|
|
|
- try {
|
|
|
- const response = await axios({
|
|
|
- url: url,
|
|
|
- method: "GET",
|
|
|
- responseType: "blob",
|
|
|
- });
|
|
|
+ // Convert to dataURL and pass to the cropper component
|
|
|
+ const file = files[0];
|
|
|
+ const reader = new FileReader();
|
|
|
+ reader.readAsDataURL(file);
|
|
|
+ reader.onload = () => {
|
|
|
+ // Update the picture source of the `img` prop
|
|
|
+ pic.value = String(reader.result);
|
|
|
|
|
|
- imageUrl.value = URL.createObjectURL(new Blob([response.data]));
|
|
|
+ // Show the modal
|
|
|
+ isShowModal.value = true;
|
|
|
|
|
|
- saveImage();
|
|
|
- } catch (error) {
|
|
|
- console.error("Error downloading the image:", error);
|
|
|
+ // Clear selected files of input element
|
|
|
+ if (!uploadInput.value) return;
|
|
|
+ uploadInput.value.value = "";
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+async function getResult() {
|
|
|
+ if (!cropper) return;
|
|
|
+ const base64 = cropper.getDataURL();
|
|
|
+ const blob = await cropper.getBlob();
|
|
|
+ if (!blob) return;
|
|
|
+
|
|
|
+ imgFile.value = await cropper.getFile({
|
|
|
+ fileName: "fileName",
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log("imgFile.value >>", imgFile.value);
|
|
|
+
|
|
|
+ result.dataURL = base64;
|
|
|
+ isShowModal.value = false;
|
|
|
+ // result.blobURL = URL.createObjectURL(blob);
|
|
|
+ // console.log({ base64, blob, file });
|
|
|
+}
|
|
|
+
|
|
|
+// 清除
|
|
|
+// function clear() {
|
|
|
+// if (!cropper) return;
|
|
|
+// cropper.clear();
|
|
|
+// }
|
|
|
+
|
|
|
+// 重置
|
|
|
+function reset() {
|
|
|
+ if (!cropper) return;
|
|
|
+ cropper.reset();
|
|
|
+}
|
|
|
+
|
|
|
+// 重新上傳
|
|
|
+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 imgFile = 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);
|
|
|
+// }
|
|
|
+
|
|
|
+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;
|
|
|
}
|
|
|
-};
|
|
|
|
|
|
-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);
|
|
|
-};
|
|
|
+ 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);
|
|
|
|
|
|
-async function share() {
|
|
|
try {
|
|
|
- // 使用 Web Share API
|
|
|
- await navigator.share(shareData);
|
|
|
- } catch (err) {
|
|
|
- // 使用者拒絕分享或發生錯誤
|
|
|
- const { name, message } = err;
|
|
|
- if (name === "AbortError") {
|
|
|
- alert("您已取消分享此相片");
|
|
|
- } else {
|
|
|
- alert(err);
|
|
|
+ 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("/step7");
|
|
|
}
|
|
|
+ } catch (error) {
|
|
|
+ console.log("error", error);
|
|
|
}
|
|
|
-
|
|
|
- // console.log("share");
|
|
|
- // if (navigator.share) {
|
|
|
- // try {
|
|
|
- // await navigator.share({
|
|
|
- // title: "101",
|
|
|
- // text: "AI明信片",
|
|
|
- // url: store.imgPath,
|
|
|
- // });
|
|
|
- // console.log("分享成功");
|
|
|
- // } catch (error) {
|
|
|
- // console.error("分享失敗", error);
|
|
|
- // }
|
|
|
- // } else {
|
|
|
- // alert("Web Share API 不支援在此設備上運行");
|
|
|
- // }
|
|
|
}
|
|
|
+
|
|
|
+const openUploadInput = () => {
|
|
|
+ if (uploadInput.value) {
|
|
|
+ uploadInput.value.click();
|
|
|
+ }
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
<div class="content main-bg">
|
|
|
- <v-container class="px-5 px-sm-15 d-flex flex-column align-center">
|
|
|
- <div id="result" class="mb-10 img-item">
|
|
|
- <img
|
|
|
- id="imageElement"
|
|
|
- ref="imageElement"
|
|
|
- class="w-100"
|
|
|
- :src="store.imgPath"
|
|
|
- alt=""
|
|
|
+ <v-container class="px-5 px-sm-15">
|
|
|
+ <div
|
|
|
+ v-if="imgLoading"
|
|
|
+ class="d-flex flex-column align-center justify-center"
|
|
|
+ >
|
|
|
+ <p class="mb-15">
|
|
|
+ {{ t("postcard.step5.text_1") }}<br />
|
|
|
+ {{ t("postcard.step5.text_2") }}
|
|
|
+ </p>
|
|
|
+
|
|
|
+ <v-progress-circular
|
|
|
+ :size="70"
|
|
|
+ :width="7"
|
|
|
+ color="white"
|
|
|
+ indeterminate
|
|
|
+ ></v-progress-circular>
|
|
|
+
|
|
|
+ <p class="mt-15">{{ t("postcard.step5.text_3") }}</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else>
|
|
|
+ <p class="title mb-5">{{ t("postcard.step5.text_4") }}</p>
|
|
|
+
|
|
|
+ <div class="d-flex justify-center">
|
|
|
+ <v-btn
|
|
|
+ @click="openUploadInput"
|
|
|
+ color="primary"
|
|
|
+ variant="outlined"
|
|
|
+ class="img-btn"
|
|
|
+ >
|
|
|
+ <span class="d-flex align-center">
|
|
|
+ <v-icon icon="mdi-camera" class="me-3 pt-1"> </v-icon>
|
|
|
+ <p>{{ t("postcard.step5.text_5") }}</p>
|
|
|
+ </span>
|
|
|
+ </v-btn>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <input
|
|
|
+ class="d-none"
|
|
|
+ ref="uploadInput"
|
|
|
+ type="file"
|
|
|
+ accept="image/jpg, image/jpeg, image/png"
|
|
|
+ @change="selectFile"
|
|
|
/>
|
|
|
- <p>{{ t(store.assignBgImg.title) }}</p>
|
|
|
+
|
|
|
+ <div v-if="isShowModal" class="mt-5">
|
|
|
+ <!-- 尺寸 4:3 -->
|
|
|
+ <VuePictureCropper
|
|
|
+ :boxStyle="{
|
|
|
+ width: '100%',
|
|
|
+ height: '100%',
|
|
|
+ backgroundColor: '#f8f8f8',
|
|
|
+ margin: 'auto',
|
|
|
+ }"
|
|
|
+ :img="pic"
|
|
|
+ :options="{
|
|
|
+ viewMode: 1,
|
|
|
+ dragMode: 'crop',
|
|
|
+ aspectRatio: 16 / 9,
|
|
|
+ }"
|
|
|
+ :presetMode="{
|
|
|
+ mode: 'fixedSize',
|
|
|
+ width: 1024,
|
|
|
+ height: 768,
|
|
|
+ }"
|
|
|
+ @ready="ready"
|
|
|
+ />
|
|
|
+
|
|
|
+ <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>
|
|
|
+
|
|
|
+ <!-- Crop result preview -->
|
|
|
+ <section v-if="result.dataURL" class="section">
|
|
|
+ <div
|
|
|
+ class="preview-img mt-5"
|
|
|
+ :style="`background-image: url('${result.dataURL}')`"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="mask"
|
|
|
+ :style="`background-image: url('${result.dataURL}')`"
|
|
|
+ ></div>
|
|
|
+ <!-- <img :src="result.dataURL" /> -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <p class="text-white mt-5">
|
|
|
+ 將人像放置於畫面右下角<br />會得到最好看的畫面唷!
|
|
|
+ </p>
|
|
|
+ </section>
|
|
|
+
|
|
|
+ <!-- <v-file-input
|
|
|
+ v-model="file"
|
|
|
+ ref="fileInput"
|
|
|
+ v-on:change="onFileChange()"
|
|
|
+ label="選擇檔案"
|
|
|
+ prepend-icon="mdi-camera"
|
|
|
+ variant="filled"
|
|
|
+ class="text-white"
|
|
|
+ ></v-file-input>
|
|
|
+
|
|
|
+ <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>
|
|
|
|
|
|
- <p
|
|
|
- class="text-start px-5 description"
|
|
|
- v-html="t(store.assignBgImg.description)"
|
|
|
- ></p>
|
|
|
- <!-- <button @click="share()" class="main-btn mt-15">分享相片</button> -->
|
|
|
- <button @click="downloadImage(store.imgPath)" class="main-btn mt-15">
|
|
|
- 儲存相片
|
|
|
- </button>
|
|
|
+ <!-- <router-link to="/step6" class="main-btn">確定</router-link> -->
|
|
|
</v-container>
|
|
|
+
|
|
|
<Footer url="/step5" />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
+// .mdi-camera::before {
|
|
|
+// color: #fff !important;
|
|
|
+// }
|
|
|
+
|
|
|
+.img-btn {
|
|
|
+ height: auto !important;
|
|
|
+ padding: 10px 20px;
|
|
|
+ font-size: 1rem;
|
|
|
+ p {
|
|
|
+ color: var(--main-color);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.preview-img {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 50vw;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-position: center center;
|
|
|
+ background-size: cover;
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ position: absolute;
|
|
|
+ content: "";
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-color: rgba(0, 0, 0, 0.7);
|
|
|
+ z-index: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .mask {
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ top: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ z-index: 15;
|
|
|
+ width: 100%;
|
|
|
+ height: 50vw;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-position: center center;
|
|
|
+ background-size: cover;
|
|
|
+ -webkit-clip-path: inset(25% 0% 0% 45% round 0);
|
|
|
+ clip-path: inset(25% 0% 0% 45% round 0);
|
|
|
+ overflow: hidden; // safari
|
|
|
+ filter: hue-rotate(0deg); // safari
|
|
|
+ // clip-path: xywh(40% 25% 70% 75% round 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // img {
|
|
|
+ // width: 100%;
|
|
|
+ // height: 30vh;
|
|
|
+ // object-fit: cover;
|
|
|
+ // position: relative;
|
|
|
+ // }
|
|
|
+}
|
|
|
+
|
|
|
.content {
|
|
|
- padding: 8rem 0 8rem;
|
|
|
min-height: 100vh;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
+}
|
|
|
|
|
|
- .img-item {
|
|
|
- img {
|
|
|
- border: 8px solid white;
|
|
|
- }
|
|
|
+.btn-content {
|
|
|
+ width: 100%;
|
|
|
+ padding: 100px 10px 20px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ // position: absolute;
|
|
|
+ // left: 50%;
|
|
|
+ // bottom: 20vw;
|
|
|
+ // transform: translate(-50%, 0);
|
|
|
|
|
|
- p {
|
|
|
- padding: 8px;
|
|
|
- margin-top: -5px;
|
|
|
- color: white;
|
|
|
- text-shadow: none;
|
|
|
- background-color: var(--main-color);
|
|
|
- }
|
|
|
+ .main-btn {
|
|
|
+ margin: 10px;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.test {
|
|
|
+ width: 300px;
|
|
|
+ height: 500px;
|
|
|
+ object-fit: cover;
|
|
|
+}
|
|
|
+
|
|
|
+.cut {
|
|
|
+ width: 500px;
|
|
|
+ height: 500px;
|
|
|
+ margin: 30px auto;
|
|
|
+}
|
|
|
</style>
|