简体中文
首屏隐私声明确认弹窗
约 1823 字大约 6 分钟
2025-04-28
企业员工首次打开内部应用时,常见诉求是:
- 应用启动时自动弹出隐私声明弹窗
- 员工需勾选所有声明后才能继续使用应用
- 系统记录确认状态,避免重复弹窗
员工首次登录企业 APP 时,首屏自动展示隐私声明及补充条款弹窗,勾选所有条款并点击 “确认” 后才可进入 APP;若未勾选或点击 “取消”,则无法使用 APP,以此确保员工明确知晓并同意相关隐私政策。
适用场景
适用于需要在应用首次启动时强制用户确认隐私声明、补充条款或合规协议的场景。
实现方案
核心目标
- 首次进入应用时自动弹出隐私声明弹窗
- 未勾选全部条款时禁止确认
- 点击取消或返回时退出应用
- 确认成功后记录状态,后续不再重复弹窗
技术架构
实现流程
- 应用启动时触发强制通知检查
- 服务端返回需要展示的弹窗组件配置
- 页面展示隐私声明及补充条款
- 用户勾选所有条款后点击“确认”
- 本地记录确认状态并返回成功结果
- 若用户点击“取消”或返回,则退出应用
基础结构与样式开发
通过wxml搭建弹窗布局,wxss定义样式,js初始化数据,实现隐私声明的展示框架。
index.wxml<view class="popup-container"> <!-- 标题区域 --> <view class="title">隐私政策确认</view> <!-- 提示文本 --> <view class="text">请阅读并勾选以下声明后,点击确认以继续使用应用</view> <!-- 声明勾选区域 --> <view class="weui-cells weui-cells_after-title"> <checkbox-group bindchange="checkboxChange"> <label class="weui-cell weui-check__label" wx:for="{{items}}" wx:key="{{item.value}}"> <view class="weui-cell__hd"> <checkbox value="{{item.value}}" checked="{{item.checked}}"/> </view> <view class="weui-cell__bd"> <view class="statement-name">{{item.name}}</view> <view class="statement-link">{{item.link}}</view> </view> </label> </checkbox-group> </view> <!-- 操作按钮区域 --> <view class="btns-wrap"> <button class="mini-btn primary" type="primary" size="mini" bindtap="sure">确认</button> <button class="mini-btn default" type="default" size="mini" bindtap="cancel">取消</button> </view> </view>index.wxss/* 弹窗容器 */ .popup-container { padding: 15px; box-sizing: border-box; } /* 标题样式 */ .title { font-size: 24px; font-weight: bold; text-align: center; line-height: 50px; margin-bottom: 10px; } /* 提示文本 */ .text { padding: 0 12px; color: #666; margin-bottom: 20px; } /* 勾选区域样式 */ .weui-cells { padding: 0 12px; } .weui-cell { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px solid #eee; } .weui-cell__hd { margin-right: 10px; } .statement-name { font-weight: 500; } .statement-link { font-size: 14px; color: #007aff; margin-top: 4px; } /* 按钮区域 */ .btns-wrap { display: flex; justify-content: center; margin-top: 30px; } .mini-btn { width: 120px; margin: 0 8px; }index.jsComponent({ // 组件数据定义 data: { // 隐私声明列表(支持动态扩展) items: [ { value: 'privacy', name: '《隐私政策》', link: 'https://m.fxiaoke.com/privacy', // 实际项目替换为真实链接 checked: false }, { value: 'privacyExtend', name: '《补充隐私条款》', link: 'https://m.fxiaoke.com/privacy/extend', // 实际项目替换为真实链接 checked: false } ] }, // 其他配置... })处理选中交互
实现勾选框的状态同步,确保用户操作能实时反馈到组件数据中。
Component({ methods: { /** * 处理勾选框状态变化 * @param {Object} e - 事件对象,包含选中的值列表 */ checkboxChange(e) { const selectedValues = e.detail.value; // 获取当前选中的所有值 const updatedItems = this.data.items.map(item => ({ ...item, // 判断当前声明是否被选中 checked: selectedValues.includes(item.value) })); // 更新组件数据,刷新视图 this.setData({ items: updatedItems }); } } })处理按钮点击
实现 “确认” 和 “取消” 按钮的逻辑,包括勾选校验、状态存储和页面跳转。
import fsApi from 'fs-hera-api'; // 引入纷享销客API Component({ methods: { /** * 确认按钮点击事件 * 校验所有声明是否勾选,通过后存储状态并进入应用 */ sure() { // 筛选未勾选的声明 const unCheckedItems = this.data.items.filter(item => !item.checked); if (unCheckedItems.length > 0) { // 存在未勾选项,提示用户 wx.showToast({ title: '请勾选所有隐私声明', icon: 'error', duration: 2000 }); return; } // 所有声明均已勾选,存储确认状态 this.saveConfirmationStatus().then(() => { // 隐藏弹窗(带动画过渡) this.setData({ show: false }); // 延迟关闭弹窗页面,确保动画完成 setTimeout(() => { // 通知上一级页面确认成功 fsApi.page.setCallbackDataThenBack({ forceRemindAgain: false, // 标记无需再次提醒 success: true }); }, 200); }); }, /** * 取消按钮点击事件 * 取消时退出应用 */ cancel() { wx.shutDownNativeApp(); // 调用原生API关闭应用 }, /** * 存储用户确认状态到本地 * @returns {Promise} - 存储操作的Promise */ saveConfirmationStatus() { return new Promise((resolve, reject) => { const storageConfig = { name: 'privacy_confirmation_status', // 自定义存储键名(建议项目统一规范) value: JSON.stringify({ confirmed: true, timestamp: new Date().getTime() // 记录确认时间 }), // 存储成功回调 onSuccess: () => resolve(), // 存储失败回调 onFail: (err) => { console.error('存储确认状态失败:', err); wx.showToast({ title: '操作失败,请重试', icon: 'error', duration: 2000 }); reject(err); } }; // 调用纷享销客存储API fsApi.jsapi.storageSetItem(storageConfig); }); } } })处理app回退
import fsApi from 'fs-hera-api'; Component({ lifetimes:{ attached() { if (wx.onBackPressed) { wx.onBackPressed({ fn: this.onBackPressed.bind(this), pageid: this.getPageId() }); } ... } }, methods: { ... onBackPressed() { wx.shutDownNativeApp(); return true; }, ... } })
关键点说明
弹窗数据结构
建议将每一条隐私声明抽象为统一对象,至少包含:
value:唯一标识name:显示名称link:条款链接checked:是否已勾选
确认逻辑
- 必须保证所有条款都勾选后才允许确认
- 确认成功后,建议同时记录确认状态和确认时间
退出逻辑
- 点击“取消”时退出应用
- 点击系统返回键时同样退出应用
- 避免用户绕过隐私确认流程进入应用
本地状态存储
- 建议使用统一命名的本地存储键
- 存储内容建议包含确认状态和时间戳
- 存储失败时应给出提示并阻止继续进入应用
完整代码
组件示例
components
statement
index.js
index.json
index.wxml
index.wxss
app.json
config.json
project.config.json
sitemap.json
components/statement/index.js
Component({
data: {
items: [
{
value: 'privacy',
name: '隐私声明',
link: 'https://m.fxiaoke.com'
},
{
value: 'privacyExtend',
name: '补充隐私声明',
link: 'https://m.fxiaoke.com'
}
]
},
methods:{
onTap() {
wx.showToast({
title: '成功',
duration: 2000
});
},
checkboxChange(e) {
const items = this.data.items
const values = e.detail.value
for (let i = 0, lenI = items.length; i < lenI; ++i) {
items[i].checked = false
for (let j = 0, lenJ = values.length; j < lenJ; ++j) {
if (items[i].value === values[j]) {
items[i].checked = true
break
}
}
}
this.setData({
items
})
},
sure(){
const noCheckItems = this.data.items.filter(item => !item.checked);
if (noCheckItems.length){
wx.showToast({
title: '需勾选所有的隐私声明才可确认',
icon: 'error'
});
}else {
this.avaStorageSetItem().then(() => {
// 先隐藏弹窗,再关闭页面,可隐藏弹窗动画
this.setData({
show: false
});
setTimeout(() => {
fsApi.page.setCallbackDataThenBack({
forceRemindAgain: false,
success: true
});
}, 200);
});
}
},
cancel(){
wx.shutDownNativeApp();
},
// 存储至本地
avaStorageSetItem() {
return new Promise(resolve => {
const num = 1 + this.data.num
let config = {
name: 'avatar_testxx_num1', // 此处需要自定义key
value: JSON.stringify({value: true}) // IOS只能存储字符串,860提供相关的api隐藏该部分
}
config.onSuccess = function () {
resolve();
}
config.onFail = function () {
console.log('set fail',num)
wx.showToast({
title: '失败',
icon: 'error',
duration: 2000
})
}
fsApi.jsapi.storageSetItem(config)
})
},
// 从本地存储获取值
avaStorageGetItem() {
return new Promise((resolve, reject) => {
let self = this
let config = {
name: 'avatar_testxx_num1'
}
config.onSuccess = function (res) {
const data = res?.value ? JSON.parse(res.value) : undefined;
resolve(data?.value);
}
config.onFail = function(res){
console.log('get fail',res)
}
fsApi.jsapi.storageGetItem(config);
})
},
onBackPressed() {
wx.shutDownNativeApp();
return true;
}
},
lifetimes:{
attached() {
if (wx.onBackPressed) {
wx.onBackPressed({
fn: this.onBackPressed.bind(this),
pageid: this.getPageId()
});
}
},
}
});components/statement/index.json
{
"component": true,
"usingComponents": {
"profile": "../profile/index"
}
}components/statement/index.wxml
<view>
<view class="title">通知</view>
<view class="text">请阅读并勾选相关声明后,点击确认,方可正常使用app</view>
<view class="weui-cells weui-cells_after-title">
<checkbox-group bindchange="checkboxChange">
<label class="weui-cell weui-check__label" wx:for="{{items}}" wx:key="{{item.value}}">
<view class="weui-cell__hd">
<checkbox value="{{item.value}}" checked="{{item.checked}}"/>
</view>
<view class="weui-cell__bd">
<view>{{item.name}}</view>
<view>{{item.link}}</view>
</view>
</label>
</checkbox-group>
</view>
<view class="btns-wrap">
<button class="mini-btn" type="primary" size="mini" bindtap="sure">确认</button>
<button class="mini-btn" type="default" size="mini" bindtap="cancel">取消</button>
</view>
</view>components/statement/index.wxss
.title {
font-size: 24px;
font-weight: bold;
text-align: center;
line-height: 50px;
}
.text {
padding: 0 12px;
}
.weui-cells {
padding: 20px;
}
.weui-cell {
display: flex;
margin: 10px 0;
align-items: center;
}
.weui-cell__hd {
margin-right: 10px;
}
.btns-wrap {
display: flex;
justify-content: center;
}
.btns-wrap .mini-btn{
margin: 0 5px;
}app.json
{
"pages": [],
"sitemapLocation": "sitemap.json"
}config.json
{
"components": {
"root": "components/statement/index"
},
"main": ""
}project.config.json
{
"miniprogramAvaId": "fsdemo-lego",
"avaFrameVer": "770.0.4",
"description": "项目配置文件",
"packOptions": {
"ignore": []
},
"setting": {
"urlCheck": true,
"scopeDataCheck": false,
"coverView": true,
"es6": true,
"postcss": true,
"compileHotReLoad": false,
"preloadBackgroundData": false,
"minified": true,
"autoAudits": false,
"newFeature": false,
"uglifyFileName": false,
"uploadWithSourceMap": true,
"useIsolateContext": true,
"nodeModules": false,
"enhance": false,
"useCompilerModule": true,
"userConfirmedUseCompilerModuleSwitch": false,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": true,
"showShadowRootInWxmlPanel": true,
"packNpmManually": false,
"packNpmRelationList": [],
"minifyWXSS": true
},
"compileType": "miniprogram",
"libVersion": "2.15.0",
"appid": "wxb7ca92fe07ce5c66",
"projectname": "legodemo",
"debugOptions": {
"hidedInDevtools": []
},
"scripts": {
"beforeCompile": ""
},
"isGameTourist": false,
"condition": {
"search": {
"list": []
},
"conversation": {
"list": []
},
"game": {
"list": []
},
"plugin": {
"list": []
},
"gamePlugin": {
"list": []
},
"miniprogram": {
"list": []
}
}
}sitemap.json
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [
{
"action": "allow",
"page": "*"
}
]
}预览效果
预览说明:视频展示了弹窗的完整交互流程,包括首次打开时强制展示、勾选校验、确认后进入应用及取消 / 返回时退出应用的效果。
常见问题
Q:为什么确认后下次还会继续弹窗?
建议排查以下问题:
- 本地存储是否写入成功
- 读取确认状态的逻辑是否与写入逻辑一致
- 存储键名是否被修改或冲突
Q:为什么点击返回没有退出应用?
建议排查以下问题:
- 是否正确注册了
wx.onBackPressed pageid是否与当前页面匹配- 回调函数是否返回了
true
Q:为什么点击确认没有继续进入应用?
建议排查以下问题:
- 是否仍有未勾选条款
- 本地存储 Promise 是否被正确 resolve
setCallbackDataThenBack是否被正常调用
