hookehuyr

feat(profile): 添加隐私政策同意弹框组件及逻辑

新增 PrivacyAgreementModal 组件用于展示隐私政策和服务协议
在个人资料页添加隐私政策弹框逻辑,未完善资料的用户需先同意协议
......@@ -15,6 +15,7 @@ declare module 'vue' {
NavBar: typeof import('./src/components/navBar.vue')['default']
NutActionSheet: typeof import('@nutui/nutui-taro')['ActionSheet']
NutButton: typeof import('@nutui/nutui-taro')['Button']
NutCheckbox: typeof import('@nutui/nutui-taro')['Checkbox']
NutCol: typeof import('@nutui/nutui-taro')['Col']
NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider']
NutDatePicker: typeof import('@nutui/nutui-taro')['DatePicker']
......@@ -42,6 +43,7 @@ declare module 'vue' {
PayCard: typeof import('./src/components/payCard.vue')['default']
Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
PrivacyAgreementModal: typeof import('./src/components/PrivacyAgreementModal.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SearchPopup: typeof import('./src/components/SearchPopup.vue')['default']
......
<template>
<nut-popup
v-model:visible="modalVisible"
position="bottom"
:closeable="false"
:close-on-click-overlay="false"
round
:safe-area-inset-bottom="true"
class="privacy-modal"
>
<view class="privacy-content">
<!-- 标题 -->
<view class="privacy-header">
<text class="privacy-title">隐私政策与用户协议</text>
</view>
<!-- 内容 -->
<view class="privacy-body">
<text class="privacy-text">
你好,小程序【手机号】涉及收集、使用和存储用户信息,增加了
<text class="privacy-link" @tap="onUserAgreementClick">《用户服务协议》</text>
<text class="privacy-link" @tap="onPrivacyPolicyClick">《隐私政策》</text>
,明确告知收集用户信息的使用目的、方式和用途,并取得用户授权同意后,才能获取用户收集用户信息。
</text>
</view>
<!-- 同意选择 -->
<view class="privacy-agreement">
<nut-checkbox v-model="agreed" class="agreement-checkbox">
<text class="agreement-text">我已阅读并同意上述协议</text>
</nut-checkbox>
</view>
<!-- 操作按钮 -->
<view class="privacy-actions">
<nut-button
class="action-btn cancel-btn"
@click="onCancel"
>
关闭
</nut-button>
<nut-button
class="action-btn confirm-btn"
type="primary"
:disabled="!agreed"
@click="onConfirm"
>
确认进入
</nut-button>
</view>
</view>
</nut-popup>
<!-- 内容全屏显示弹框 -->
<nut-popup
v-model:visible="showContentModal"
position="right"
:closeable="true"
:close-on-click-overlay="true"
:safe-area-inset-bottom="true"
:style="{ width: '100%', height: '100%' }"
@close="onCloseContentModal"
>
<view class="content-container">
<!-- 标题栏 -->
<view class="content-header">
<text class="content-title">{{ contentTitle }}</text>
<view class="close-btn" @click="onCloseContentModal">
<text class="close-text">×</text>
</view>
</view>
<!-- 内容区域 -->
<scroll-view class="content-scroll" :scroll-y="true">
<view class="content-body">
<view class="content-text" v-html="contentText"></view>
</view>
</scroll-view>
</view>
</nut-popup>
</template>
<script setup>
import { ref, computed, defineEmits, defineProps } from 'vue'
import Taro from '@tarojs/taro'
// Props
const props = defineProps({
visible: {
type: Boolean,
default: false
}
})
// Emits
const emit = defineEmits(['update:visible', 'confirm', 'cancel'])
// 响应式数据
const agreed = ref(false)
// 计算属性
const modalVisible = computed({
get() {
return props.visible
},
set(value) {
emit('update:visible', value)
}
})
// 用户服务协议内容
const userAgreementContent = `
<div class="agreement-section">
<div class="section-title">第一条 协议的范围</div>
<div class="section-content">
<div class="clause-item">1.1 本协议是您与捡个电驴小程序(以下简称"本平台")之间关于您使用本平台服务所订立的协议。</div>
<div class="clause-item">1.2 本协议描述本平台与用户之间关于"捡个电驴"服务相关的权利义务。</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">第二条 服务内容</div>
<div class="section-content">
<div class="clause-item">2.1 本平台为用户提供二手电动车交易信息发布、浏览、联系等服务。</div>
<div class="clause-item">2.2 用户可以通过本平台发布车辆出售信息、浏览车辆信息、联系买卖双方等。</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">第三条 用户注册</div>
<div class="section-content">
<div class="clause-item">3.1 用户需要注册账户才能使用本平台的完整服务。</div>
<div class="clause-item">3.2 用户应提供真实、准确、完整的个人信息。</div>
<div class="clause-item">3.3 用户应妥善保管账户信息,对账户下的所有活动承担责任。</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">第四条 用户行为规范</div>
<div class="section-content">
<div class="clause-item">4.1 用户不得发布虚假信息或进行欺诈行为。</div>
<div class="clause-item">4.2 用户不得发布违法违规内容。</div>
<div class="clause-item">4.3 用户应遵守平台的交易规则和社区准则。</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">第五条 隐私保护</div>
<div class="section-content">
<div class="clause-item">5.1 本平台重视用户隐私保护,具体内容请参见《隐私政策》。</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">第六条 免责声明</div>
<div class="section-content">
<div class="clause-item">6.1 本平台仅提供信息发布平台,不参与具体交易。</div>
<div class="clause-item">6.2 用户之间的交易风险由用户自行承担。</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">第七条 协议修改</div>
<div class="section-content">
<div class="clause-item">7.1 本平台有权根据需要修改本协议。</div>
<div class="clause-item">7.2 修改后的协议将在平台上公布。</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">第八条 争议解决</div>
<div class="section-content">
<div class="clause-item">8.1 因本协议产生的争议,双方应友好协商解决。</div>
<div class="clause-item">8.2 协商不成的,可向有管辖权的人民法院提起诉讼。</div>
</div>
</div>
<div class="agreement-footer">本协议自用户点击同意之日起生效。</div>
`
// 隐私政策内容
const privacyPolicyContent = `
<div class="agreement-section">
<div class="section-title">引言</div>
<div class="section-content">
<div class="clause-item">捡个电驴小程序(以下简称"我们")非常重视用户的隐私保护。本隐私政策说明了我们如何收集、使用、存储和保护您的个人信息。</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">一、我们收集的信息</div>
<div class="section-content">
<div class="clause-item">1.1 您主动提供的信息</div>
<div class="clause-list">
<div class="list-item1">注册信息:手机号码、昵称、头像等</div>
<div class="list-item1">个人资料:性别、学校、生日等</div>
<div class="list-item1">车辆信息:发布的车辆详情、图片等</div>
<div class="list-item1">联系信息:与其他用户的沟通记录</div>
</div>
<div class="clause-item">1.2 自动收集的信息</div>
<div class="clause-list">
<div class="list-item1">设备信息:设备型号、操作系统版本等</div>
<div class="list-item1">使用信息:访问时间、使用功能、操作记录等</div>
<div class="list-item1">位置信息:用于提供本地化服务(需要您的授权)</div>
</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">二、信息使用目的</div>
<div class="section-content">
<div class="clause-item">2.1 提供服务</div>
<div class="clause-list">
<div class="list-item1">账户注册和身份验证</div>
<div class="list-item1">车辆信息发布和展示</div>
<div class="list-item1">用户间沟通和交易撮合</div>
<div class="list-item1">个性化推荐服务</div>
</div>
<div class="clause-item">2.2 改善服务</div>
<div class="clause-list">
<div class="list-item1">分析用户使用习惯</div>
<div class="list-item1">优化产品功能</div>
<div class="list-item1">提升用户体验</div>
</div>
<div class="clause-item">2.3 安全保障</div>
<div class="clause-list">
<div class="list-item1">防范欺诈和违法行为</div>
<div class="list-item1">保护用户和平台安全</div>
<div class="list-item1">维护正常的服务秩序</div>
</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">三、信息共享</div>
<div class="section-content">
<div class="clause-item">3.1 我们不会出售您的个人信息</div>
<div class="clause-item">3.2 在以下情况下,我们可能会共享您的信息:</div>
<div class="clause-list">
<div class="list-item1">获得您的明确同意</div>
<div class="list-item1">法律法规要求</div>
<div class="list-item1">保护用户或公众的重大利益</div>
<div class="list-item1">与服务提供商共享(仅限于提供服务所需)</div>
</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">四、信息存储和保护</div>
<div class="section-content">
<div class="clause-item">4.1 存储期限</div>
<div class="clause-list">
<div class="list-item1">在您使用服务期间持续存储</div>
<div class="list-item1">账户注销后,我们将删除或匿名化处理您的个人信息</div>
</div>
<div class="clause-item">4.2 安全措施</div>
<div class="clause-list">
<div class="list-item1">采用行业标准的安全技术</div>
<div class="list-item1">定期进行安全评估</div>
<div class="list-item1">限制员工访问权限</div>
<div class="list-item1">建立数据泄露应急响应机制</div>
</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">五、您的权利</div>
<div class="section-content">
<div class="clause-item">5.1 访问权:您有权了解我们收集的关于您的个人信息</div>
<div class="clause-item">5.2 更正权:您有权要求我们更正不准确的个人信息</div>
<div class="clause-item">5.3 删除权:在特定情况下,您有权要求我们删除您的个人信息</div>
<div class="clause-item">5.4 撤回同意:您有权随时撤回对个人信息处理的同意</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">六、未成年人保护</div>
<div class="section-content">
<div class="clause-item">我们不会故意收集未满14周岁儿童的个人信息。如果您是未成年人,请在监护人指导下使用我们的服务。</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">七、第三方服务</div>
<div class="section-content">
<div class="clause-item">我们的服务可能包含第三方链接或服务,这些第三方有自己的隐私政策,我们不对其隐私做法承担责任。</div>
</div>
</div>
<div class="agreement-section">
<div class="section-title">八、政策更新</div>
<div class="section-content">
<div class="clause-item">我们可能会不时更新本隐私政策。重大变更时,我们会通过适当方式通知您。</div>
</div>
</div>
<div class="agreement-footer">本隐私政策最后更新日期:2024年7月29日</div>
`
// 显示内容的状态
const showContentModal = ref(false)
const contentTitle = ref('')
const contentText = ref('')
/**
* 点击用户服务协议
*/
const onUserAgreementClick = () => {
contentTitle.value = '用户服务协议'
contentText.value = userAgreementContent
showContentModal.value = true
}
/**
* 点击隐私政策
*/
const onPrivacyPolicyClick = () => {
contentTitle.value = '隐私政策'
contentText.value = privacyPolicyContent
showContentModal.value = true
}
/**
* 关闭内容弹框
*/
const onCloseContentModal = () => {
showContentModal.value = false
contentTitle.value = ''
contentText.value = ''
}
/**
* 取消操作
*/
const onCancel = () => {
agreed.value = false
emit('update:visible', false)
emit('cancel')
}
/**
* 确认操作
*/
const onConfirm = () => {
if (!agreed.value) {
Taro.showToast({
title: '请先同意协议',
icon: 'none'
})
return
}
emit('update:visible', false)
emit('confirm')
}
</script>
<style lang="less">
.privacy-modal {
// width: 680rpx;
}
.privacy-content {
padding: 40rpx 32rpx;
background: white;
border-radius: 16rpx;
}
.privacy-header {
text-align: center;
margin-bottom: 32rpx;
}
.privacy-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.privacy-body {
margin-bottom: 32rpx;
}
.privacy-text {
font-size: 28rpx;
line-height: 1.6;
color: #666;
}
.privacy-link {
color: #fb923c;
text-decoration: underline;
font-weight: 500;
}
.privacy-agreement {
margin-bottom: 32rpx;
padding: 16rpx 0;
}
.agreement-checkbox {
display: flex;
align-items: center;
}
.agreement-text {
font-size: 28rpx;
color: #666;
margin-left: 16rpx;
}
.privacy-actions {
display: flex;
gap: 24rpx;
}
.action-btn {
flex: 1;
height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
}
.cancel-btn {
background: #f5f5f5;
color: #666;
border: none;
}
.confirm-btn {
background: #fb923c;
border: none;
}
.confirm-btn:disabled {
background: #ccc;
color: #999;
}
/* 内容弹框样式 */
.content-modal {
width: 100vw;
height: 100vh;
}
.content-container {
width: 100%;
height: 100vh;
background: white;
display: flex;
flex-direction: column;
}
.content-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx 40rpx;
border-bottom: 1rpx solid #eee;
background: white;
position: sticky;
top: 0;
z-index: 10;
}
.content-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.close-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 50%;
cursor: pointer;
}
.close-text {
font-size: 40rpx;
color: #666;
line-height: 1;
}
.content-scroll {
flex: 1;
height: 0;
}
.content-body {
padding: 40rpx;
}
// .content-text {
// font-size: 28rpx;
// line-height: 1.8;
// color: #333;
// white-space: pre-line;
// word-wrap: break-word;
// }
// 协议内容样式
.agreement-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
text-align: center;
margin-bottom: 40rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #e5e5e5;
}
.agreement-section {
margin-bottom: 30rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
padding-left: 10rpx;
border-left: 6rpx solid #007aff;
}
.section-content {
padding-left: 20rpx;
}
.clause-item {
font-size: 28rpx;
color: #666;
line-height: 1.6;
margin-bottom: 15rpx;
}
.clause-list {
margin: 15rpx 0;
padding-left: 20rpx;
}
.list-item {
font-size: 26rpx;
color: #666;
line-height: 1.5;
margin-bottom: 10rpx;
padding-left: 20rpx;
position: relative;
}
.list-item::before {
content: '•';
position: absolute;
left: 0;
color: #007aff;
font-weight: bold;
}
.list-item1 {
font-size: 26rpx;
color: #666;
line-height: 1.5;
margin-bottom: 10rpx;
padding-left: 20rpx;
position: relative;
}
.list-item1::before {
content: '•';
position: absolute;
left: 0;
color: #007aff;
font-weight: bold;
}
.agreement-footer {
font-size: 28rpx;
color: #999;
text-align: center;
margin-top: 40rpx;
padding-top: 20rpx;
border-top: 2rpx solid #e5e5e5;
font-style: italic;
}
</style>
......@@ -74,16 +74,23 @@
<!-- 自定义TabBar -->
<TabBar />
<!-- 隐私政策同意弹框 -->
<PrivacyAgreementModal
v-model:visible="showPrivacyModal"
@confirm="onPrivacyConfirm"
@cancel="onPrivacyCancel"
/>
</view>
</template>
<script setup>
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { Heart, Clock, Notice, Cart, Message, Tips, Right, StarN } from '@nutui/icons-vue-taro'
import Taro, { useDidShow } from '@tarojs/taro'
import TabBar from '@/components/TabBar.vue'
import PrivacyAgreementModal from '@/components/PrivacyAgreementModal.vue'
import { useUserStore } from '@/stores/user'
// import { checkPermission, PERMISSION_TYPES } from '@/utils/permission'
// 默认头像
const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
......@@ -94,6 +101,9 @@ const userStore = useUserStore()
// 用户信息计算属性
const userInfo = computed(() => userStore.userInfo)
// 隐私政策弹框状态
const showPrivacyModal = ref(false)
useDidShow(async () => {
await userStore.fetchUserInfo()
})
......@@ -101,10 +111,36 @@ useDidShow(async () => {
/**
* 编辑个人资料
*/
const onEditProfile = () => {
const onEditProfile = async () => {
// 检查用户是否已完善资料
const hasCompleteProfile = userStore.hasCompleteProfile
if (hasCompleteProfile) {
// 已完善资料,直接进入编辑页面
Taro.navigateTo({
url: '/pages/editProfile/index'
})
} else {
// 未完善资料,显示隐私政策同意弹框
showPrivacyModal.value = true
}
}
/**
* 隐私政策确认
*/
const onPrivacyConfirm = () => {
// 用户同意隐私政策后,跳转到编辑资料页面
Taro.navigateTo({
url: '/pages/editProfile/index'
})
}
/**
* 隐私政策取消
*/
const onPrivacyCancel = () => {
// 用户取消,不做任何操作
}
/**
......