diff --git a/src/components/AppFooter.vue b/src/components/AppFooter.vue index 25a4746..9aaa67f 100644 --- a/src/components/AppFooter.vue +++ b/src/components/AppFooter.vue @@ -10,18 +10,18 @@ > 網站原始碼 - - 服務條款 - 隱私權政策 + + 服務條款 +
© {{ year }} diff --git a/src/plugins/turnstile.js b/src/plugins/turnstile.js index f305fbf..2f9e080 100644 --- a/src/plugins/turnstile.js +++ b/src/plugins/turnstile.js @@ -1,4 +1,4 @@ -import {onMounted, onBeforeUnmount} from "vue"; +import {onBeforeUnmount} from "vue"; const {VITE_TURNSTILE_SITE_KEY: turnstileSiteKey} = import.meta.env; const scriptSourceUrl = "https://challenges.cloudflare.com/turnstile/v0/api.js?onload=loadTurnstile"; @@ -18,7 +18,12 @@ export function loadTurnstile(selector, callback) { if (!turnstileSiteKey) { throw new Error("turnstileSiteKey is required for loadTurnstile"); } - document.head.appendChild(turnstileScript); + if (!selector) { + throw new Error("selector is required for loadTurnstile"); + } + if (!document.head.contains(turnstileScript)) { + document.head.appendChild(turnstileScript); + } window.loadTurnstile = () => { window.turnstile.render(selector, { sitekey: turnstileSiteKey, @@ -34,6 +39,9 @@ export function loadTurnstile(selector, callback) { * @returns {void} */ export function unloadTurnstile() { + if (!document.head.contains(turnstileScript)) { + return; + } document.head.removeChild(turnstileScript); } @@ -41,22 +49,26 @@ export function unloadTurnstile() { * Vue 3 composition function to use the Turnstile widget. * @module turnstile * @function - * @param {string} selector - The CSS selector to render the Turnstile widget. - * @returns {Promise} The promise that resolves when the Turnstile widget is solved. + * @returns {object} The methods to use the Turnstile widget. */ -export function useTurnstile(selector) { - if (!selector) { - throw new Error("selector is required for useTurnstile"); - } - return new Promise((resolve) => { - // Attach the Turnstile script when the component is mounted. - onMounted( - () => loadTurnstile(selector, resolve), - ); +export function useTurnstile() { + // Detach the Turnstile script when the component is unmounted. + onBeforeUnmount( + () => unloadTurnstile(), + ); - // Detach the Turnstile script when the component is unmounted. - onBeforeUnmount( - () => unloadTurnstile(), - ); - }); + let resolvedValue = null; + return { + render: (selector) => { + resolvedValue = new Promise((resolve) => { + loadTurnstile(selector, resolve); + }); + }, + token: () => { + return resolvedValue; + }, + clear: () => { + resolvedValue = null; + }, + }; } diff --git a/src/views/RoomManageView.vue b/src/views/RoomManageView.vue index 2890b01..45d739f 100644 --- a/src/views/RoomManageView.vue +++ b/src/views/RoomManageView.vue @@ -78,7 +78,7 @@ @click="onClickRefresh" > @@ -125,6 +125,8 @@ const { const isLoad = ref(false); const isDead = ref(false); + +const isLoadRefresh = ref(false); const isCopy = ref(false); const statusMessage = ref(""); @@ -175,11 +177,11 @@ const onClickCopyInviteUrl = async () => { }; const onClickRefresh = async () => { - isLoad.value = true; + isLoadRefresh.value = true; const result = await client.patch(`rooms/${roomCode}`).json(); Object.assign(roomData, result); statusMessage.value = "更新成功"; - isLoad.value = false; + isLoadRefresh.value = false; }; const onClickCommit = () => { diff --git a/src/views/RoomSubmissionView.vue b/src/views/RoomSubmissionView.vue index dc00dbb..190273c 100644 --- a/src/views/RoomSubmissionView.vue +++ b/src/views/RoomSubmissionView.vue @@ -54,7 +54,7 @@ @click="onClickAction" >
{ } submissionData.code = submissionCode.value; - isLoad.value = true; + isLoadAction.value = true; isQuery.value = true; try { const submission = await client.get( @@ -218,7 +220,7 @@ const onClickAction = async () => { } catch (error) { console.error(error); } - isLoad.value = false; + isLoadAction.value = false; }; onMounted(async () => { diff --git a/src/views/TicketView.vue b/src/views/TicketView.vue index a3a6bf7..3017624 100644 --- a/src/views/TicketView.vue +++ b/src/views/TicketView.vue @@ -87,7 +87,7 @@ @click="onSubmit" > 加入社群 @@ -117,7 +117,7 @@ import {useTurnstile} from "../plugins/turnstile.js"; const route = useRoute(); const client = useClient(); -const turnstile = useTurnstile("#captcha"); +const turnstile = useTurnstile(); const { roomCode, @@ -125,6 +125,8 @@ const { const isLoad = ref(false); const isDead = ref(false); + +const isLoadSubmit = ref(false); const isCopy = ref(false); const statusMessage = ref(""); @@ -156,10 +158,6 @@ const backgroundStyle = computed(() => { }; }); -turnstile.then((token) => { - captchaToken.value = token; -}); - const onClickCopy = async () => { if (!navigator.clipboard) { statusMessage.value = "您的瀏覽器不支援複製功能"; @@ -190,7 +188,7 @@ const onSubmit = async () => { return; } - isLoad.value = true; + isLoadSubmit.value = true; const result = await client.post("submissions", { json: { captcha: captchaToken.value, @@ -198,7 +196,7 @@ const onSubmit = async () => { }, }).json(); submissionCode.value = result.code; - isLoad.value = false; + isLoadSubmit.value = false; }; onMounted(async () => { @@ -211,5 +209,10 @@ onMounted(async () => { console.error(error); } isLoad.value = false; + + if (!isDead.value) { + turnstile.render("#captcha"); + captchaToken.value = await turnstile.token(); + } });