feat: 個資條款

This commit is contained in:
skytek_xinliang
2026-09-22 09:16:14 +08:00
parent fdc93142d9
commit 92c5163334
5 changed files with 88 additions and 23 deletions
@@ -0,0 +1,39 @@
<script setup lang="ts">
const isOpen = defineModel<boolean>({ required: true });
</script>
<template>
<v-dialog v-model="isOpen" max-width="500">
<v-card class="rounded-xl">
<v-card-title
class="bg-primary text-white font-weight-bold d-flex align-center"
>
<v-icon class="mr-2" icon="mdi-shield-check" />
個資同意與資料授權
</v-card-title>
<v-card-text class="pt-4 text-body-1">
<p class="mb-3">
「運動玩轉健康力」平台非常重視您的個人隱私。我們所蒐集的身體活動自評、血壓、體脂以及現場運動表現數據,將僅用於以下用途:
</p>
<ul class="pl-4 mb-3">
<li>提供個人化的運動與健康改善建議。</li>
<li>於活動現場合對您的身分。</li>
<li>在去識別化的安全機制下,作為高齡健康促進的政策分析參考。</li>
</ul>
</v-card-text>
<v-card-actions class="justify-end px-4 pb-4">
<v-btn
class="rounded-lg px-4"
color="primary"
variant="flat"
@click="isOpen = false"
>
確認關閉
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
@@ -91,6 +91,15 @@
<!-- 主要功能連結 --> <!-- 主要功能連結 -->
<v-list density="comfortable" nav> <v-list density="comfortable" nav>
<v-list-item
class="py-2"
color="primary"
exact
prepend-icon="mdi-view-dashboard"
title="計畫簡介"
to="/"
/>
<v-list-item <v-list-item
v-if="store.isLoggedIn" v-if="store.isLoggedIn"
color="primary" color="primary"
+20 -10
View File
@@ -2,14 +2,7 @@
<Layout> <Layout>
<template #main> <template #main>
<div class="profile-page"> <div class="profile-page">
<v-card class="mb-6" variant="flat"> <PageHeading title="個人資料修改" />
<v-card-title class="text-primary font-weight-bold"
>個人資料修改</v-card-title
>
<v-card-subtitle class="text-grey-darken-3">
更新您的基本資料與緊急聯絡資訊。</v-card-subtitle
>
</v-card>
<v-card class="rounded-xl" color="surface" variant="flat"> <v-card class="rounded-xl" color="surface" variant="flat">
<v-card-title class="mb-4">基本資料</v-card-title> <v-card-title class="mb-4">基本資料</v-card-title>
@@ -168,8 +161,20 @@
class="mb-3" class="mb-3"
color="primary" color="primary"
:disabled="initialLoading" :disabled="initialLoading"
label="我已閱讀並同意個資授權條款" >
/> <template #label>
<span>
我已閱讀並同意
<button
class="text-primary text-decoration-underline"
type="button"
@click.stop="showPrivacyConsent = true"
>
個資授權條款
</button>
</span>
</template>
</v-checkbox>
<div class="d-flex flex-column flex-sm-row ga-3"> <div class="d-flex flex-column flex-sm-row ga-3">
<v-btn <v-btn
@@ -195,13 +200,17 @@
</v-card-text> </v-card-text>
</v-card> </v-card>
</div> </div>
<PrivacyConsentDialog v-model="showPrivacyConsent" />
</template> </template>
</Layout> </Layout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, onMounted, reactive, ref } from "vue"; import { computed, nextTick, onMounted, reactive, ref } from "vue";
import PrivacyConsentDialog from "@/components/PrivacyConsentDialog.vue";
import Layout from "@/components/layout/DefaultLayout.vue"; import Layout from "@/components/layout/DefaultLayout.vue";
import PageHeading from "@/components/layout/PageHeading.vue";
import type { UserProfile } from "@/stores/app"; import type { UserProfile } from "@/stores/app";
import { useAppStore } from "@/stores/app"; import { useAppStore } from "@/stores/app";
import { getApiErrorMessage } from "@/utils/api-error"; import { getApiErrorMessage } from "@/utils/api-error";
@@ -221,6 +230,7 @@ interface ProfileForm {
} }
const store = useAppStore(); const store = useAppStore();
const showPrivacyConsent = ref(false);
const form = reactive<ProfileForm>(createEmptyForm()); const form = reactive<ProfileForm>(createEmptyForm());
const initialLoading = ref(true); const initialLoading = ref(true);
const saving = ref(false); const saving = ref(false);
+18 -6
View File
@@ -146,12 +146,20 @@
</v-col> </v-col>
</v-row> </v-row>
<v-checkbox <v-checkbox v-model="form.consent" class="mb-3" color="primary">
v-model="form.consent" <template #label>
class="mb-3" <span>
color="primary" 我已閱讀並同意
label="我已閱讀並同意個資授權條款" <button
/> class="text-primary text-decoration-underline"
type="button"
@click.stop="showPrivacyConsent = true"
>
個資授權條款
</button>
</span>
</template>
</v-checkbox>
<v-btn <v-btn
block block
@@ -174,6 +182,8 @@
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>
<PrivacyConsentDialog v-model="showPrivacyConsent" />
</v-app> </v-app>
</template> </template>
@@ -183,6 +193,7 @@ import { useRoute, useRouter } from "vue-router";
import type { Gender, RegisterRequest } from "@/api/types"; import type { Gender, RegisterRequest } from "@/api/types";
import { CONSENT_VERSION } from "@/api/types"; import { CONSENT_VERSION } from "@/api/types";
import BrandAvatar from "@/components/BrandAvatar.vue"; import BrandAvatar from "@/components/BrandAvatar.vue";
import PrivacyConsentDialog from "@/components/PrivacyConsentDialog.vue";
import { useAppStore } from "@/stores/app"; import { useAppStore } from "@/stores/app";
import { import {
getApiErrorStatus, getApiErrorStatus,
@@ -197,6 +208,7 @@ import {
const store = useAppStore(); const store = useAppStore();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const showPrivacyConsent = ref(false);
const existingDraft = readGuestSelfAssessmentDraft(); const existingDraft = readGuestSelfAssessmentDraft();
const routePhone = normalizePhone(String(route.query.phone ?? "")); const routePhone = normalizePhone(String(route.query.phone ?? ""));
const initialProfile = existingDraft?.profile; const initialProfile = existingDraft?.profile;
@@ -1,13 +1,7 @@
<template> <template>
<Layout> <Layout>
<template #main> <template #main>
<v-card class="mb-6" variant="flat"> <PageHeading title="線上健康自評" />
<v-card-title class="text-primary">線上健康自評</v-card-title>
<v-card-subtitle class="text-grey-darken-3">
只需 3 分鐘,評估您的運動風險與健康狀態
</v-card-subtitle>
</v-card>
<!-- 進度指示器 --> <!-- 進度指示器 -->
<v-row class="mb-6 justify-center"> <v-row class="mb-6 justify-center">
@@ -526,6 +520,7 @@ import type {
} from "@/api/types"; } from "@/api/types";
import { CONSENT_VERSION } from "@/api/types"; import { CONSENT_VERSION } from "@/api/types";
import Layout from "@/components/layout/DefaultLayout.vue"; import Layout from "@/components/layout/DefaultLayout.vue";
import PageHeading from "@/components/layout/PageHeading.vue";
import { APPOINTMENT_ENABLED } from "@/config/features"; import { APPOINTMENT_ENABLED } from "@/config/features";
import { import {
readGuestSelfAssessmentDraft, readGuestSelfAssessmentDraft,