feat(miniapp): add privacy consent and legal pages

Show privacy popup on launch, gate login on legal consent, and link
user agreement and privacy policy pages in the mini program.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-12 12:18:54 +08:00
parent 8928827291
commit 048f25f580
29 changed files with 817 additions and 8 deletions
+11
View File
@@ -22,7 +22,18 @@ The H5 APIs are reused as-is. Native Mini Program login posts the `wx.login` cod
- `utils/config.js` 默认 `LOCAL_DEV = false`API 指向 **`https://m.tkmind.cn`**。
- `project.config.json``urlCheck: true`;DevTools 需配置真实小程序 AppID(非 tourist)。
- 微信公众平台需配置 request / web-view 合法域名:`m.tkmind.cn`
- 已启用 `__usePrivacyCheck__`,登录页含协议勾选(默认不勾选)、协议全文页与微信隐私弹窗组件。
- 首次登录须主动勾选协议;登录成功后本地保存 cookie 与同意记录,下次自动登录不再展示协议区。
- 此配置**不会**触发 103 发版;仅小程序客户端连线上 Portal。
### 微信公众平台后台(提审必配)
1. **设置 → 服务内容声明 → 用户隐私保护指引**
- 声明收集:用户账号、邮箱、微信登录标识、聊天记录、设备信息
- 用途:账号登录验证、身份识别、提供 AI 对话与会话服务
2. **设置 → 基本设置 → 服务内容声明**
- 补充用户服务协议与隐私政策说明(协议已内置在小程序 `pages/legal/`
3. 提审备注可写:「登录页已增加协议勾选与隐私政策全文,未同意前不提交用户信息;已接入微信隐私保护检测」
- **微信一键登录**还需 Portal 侧配置 `H5_WECHAT_MINIAPP_APP_ID` / `H5_WECHAT_MINIAPP_APP_SECRET` 并实现 `/auth/wechat-miniapp/login`;未部署前可先用账号密码登录。
### 开发者工具仍显示「游客模式」时
+4 -1
View File
@@ -1,12 +1,14 @@
const { getStoredSession, setApiBaseUrl, getMiniProgramAppId, isTouristMode, getApiBaseUrl } = require('./utils/api');
const { API_BASE_URL } = require('./utils/config');
const { getPrivacySetting } = require('./utils/privacy');
App({
globalData: {
user: null,
privacySetting: null,
},
onLaunch() {
async onLaunch() {
setApiBaseUrl(API_BASE_URL);
const appId = getMiniProgramAppId();
console.info('[TKMind miniapp] API_BASE_URL =', getApiBaseUrl() || API_BASE_URL);
@@ -18,5 +20,6 @@ App({
if (session?.user) {
this.globalData.user = session.user;
}
this.globalData.privacySetting = await getPrivacySetting();
},
});
+5 -1
View File
@@ -4,8 +4,12 @@
"pages/chat/index",
"pages/sessions/index",
"pages/space/index",
"pages/webview/index"
"pages/webview/index",
"pages/legal/index/index",
"pages/legal/privacy/index",
"pages/legal/terms/index"
],
"__usePrivacyCheck__": true,
"window": {
"navigationBarTitleText": "TKMind",
"navigationBarBackgroundColor": "#101820",
+45
View File
@@ -0,0 +1,45 @@
const { openPrivacyContract } = require('../../utils/privacy');
Component({
data: {
show: false,
privacyContractName: '《用户隐私保护指引》',
},
lifetimes: {
attached() {
if (typeof wx.onNeedPrivacyAuthorization !== 'function') {
return;
}
wx.onNeedPrivacyAuthorization((resolve) => {
this._privacyResolve = resolve;
this.setData({ show: true });
});
},
},
methods: {
openPrivacyContract() {
openPrivacyContract();
},
handleDisagree() {
this.setData({ show: false });
if (this._privacyResolve) {
this._privacyResolve({ event: 'disagree' });
this._privacyResolve = null;
}
},
handleAgree() {
this.setData({ show: false });
if (this._privacyResolve) {
this._privacyResolve({
buttonId: 'agree-privacy-btn',
event: 'agree',
});
this._privacyResolve = null;
}
},
},
});
@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}
@@ -0,0 +1,21 @@
<view wx:if="{{show}}" class="privacy-mask">
<view class="privacy-panel">
<text class="privacy-title">用户隐私保护提示</text>
<view class="privacy-body">
<text>在使用 TKMind 前,请阅读</text>
<text class="privacy-link" bindtap="openPrivacyContract">{{privacyContractName}}</text>
<text>。点击「同意」即表示你已阅读并同意该指引。</text>
</view>
<view class="privacy-actions">
<button class="privacy-decline" bindtap="handleDisagree">拒绝</button>
<button
id="agree-privacy-btn"
class="privacy-accept"
open-type="agreePrivacyAuthorization"
bindagreeprivacyauthorization="handleAgree"
>
同意
</button>
</view>
</view>
</view>
@@ -0,0 +1,65 @@
.privacy-mask {
position: fixed;
inset: 0;
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
background: rgba(15, 23, 42, 0.55);
box-sizing: border-box;
}
.privacy-panel {
width: 100%;
max-width: 640rpx;
padding: 36rpx;
border-radius: 16rpx;
background: #ffffff;
box-shadow: 0 10rpx 30rpx rgba(15, 23, 42, 0.12);
}
.privacy-title {
display: block;
font-size: 34rpx;
font-weight: 800;
color: #101820;
}
.privacy-body {
margin-top: 24rpx;
font-size: 26rpx;
color: #334155;
line-height: 1.8;
}
.privacy-link {
color: #0f766e;
font-weight: 600;
}
.privacy-actions {
display: flex;
gap: 16rpx;
margin-top: 32rpx;
}
.privacy-decline,
.privacy-accept {
flex: 1;
height: 80rpx;
border-radius: 12rpx;
font-size: 28rpx;
font-weight: 700;
line-height: 80rpx;
}
.privacy-decline {
background: #f1f5f9;
color: #475569;
}
.privacy-accept {
background: #0f766e;
color: #ffffff;
}
+4
View File
@@ -206,6 +206,10 @@ Page({
this.setData({ sessionId: '', messages: [], draft: '', error: '' });
},
openLegalCenter() {
wx.navigateTo({ url: '/pages/legal/index/index' });
},
openLink(event) {
let url = String(event.currentTarget.dataset.url || '').trim();
if (!url) {
+4 -1
View File
@@ -1,3 +1,6 @@
{
"navigationBarTitleText": "TKMind 聊天"
"navigationBarTitleText": "TKMind 聊天",
"usingComponents": {
"privacy-popup": "/components/privacy-popup/index"
}
}
+3
View File
@@ -6,6 +6,7 @@
</view>
<view class="header-actions">
<button class="header-button" bindtap="startNewChat">新聊天</button>
<button class="header-button" bindtap="openLegalCenter">隐私</button>
<button class="header-button" bindtap="handleLogout">退出</button>
</view>
</view>
@@ -71,3 +72,5 @@
<button class="send" loading="{{loading}}" disabled="{{loading}}" bindtap="sendMessage">发送</button>
</view>
</view>
<privacy-popup />
+15
View File
@@ -0,0 +1,15 @@
const { openPrivacyContract } = require('../../../utils/privacy');
Page({
openTerms() {
wx.navigateTo({ url: '/pages/legal/terms/index' });
},
openPrivacy() {
wx.navigateTo({ url: '/pages/legal/privacy/index' });
},
openWechatPrivacy() {
openPrivacyContract();
},
});
+3
View File
@@ -0,0 +1,3 @@
{
"navigationBarTitleText": "隐私与协议"
}
+23
View File
@@ -0,0 +1,23 @@
<view class="page legal-hub-page">
<view class="panel legal-hub-card">
<text class="legal-hub-title">隐私与协议</text>
<text class="legal-hub-desc">
你可以随时查看 TKMind 的用户服务协议与隐私政策,了解我们如何收集、使用与保护你的个人信息。
</text>
<view class="legal-hub-item" bindtap="openTerms">
<text class="legal-hub-item-title">用户服务协议</text>
<text class="legal-hub-item-desc">服务范围、账号使用规范与免责声明</text>
</view>
<view class="legal-hub-item" bindtap="openPrivacy">
<text class="legal-hub-item-title">隐私政策</text>
<text class="legal-hub-item-desc">个人信息收集目的、方式、用途与您的权利</text>
</view>
<view class="legal-hub-item" bindtap="openWechatPrivacy">
<text class="legal-hub-item-title">微信用户隐私保护指引</text>
<text class="legal-hub-item-desc">查看微信平台提供的隐私保护说明</text>
</view>
</view>
</view>
+44
View File
@@ -0,0 +1,44 @@
.legal-hub-page {
padding-top: 24rpx;
}
.legal-hub-card {
display: flex;
flex-direction: column;
gap: 24rpx;
padding: 32rpx;
}
.legal-hub-title {
font-size: 40rpx;
font-weight: 800;
color: #101820;
}
.legal-hub-desc {
font-size: 26rpx;
color: #475569;
line-height: 1.8;
}
.legal-hub-item {
padding: 24rpx;
border: 1rpx solid #dbe3eb;
border-radius: 12rpx;
background: #f8fafc;
}
.legal-hub-item-title {
display: block;
font-size: 30rpx;
font-weight: 700;
color: #0f766e;
}
.legal-hub-item-desc {
display: block;
margin-top: 8rpx;
font-size: 24rpx;
color: #64748b;
line-height: 1.6;
}
+1
View File
@@ -0,0 +1 @@
Page({});
+3
View File
@@ -0,0 +1,3 @@
{
"navigationBarTitleText": "隐私政策"
}
+87
View File
@@ -0,0 +1,87 @@
<view class="page legal-page">
<view class="panel legal-card">
<text class="legal-title">TKMind 隐私政策</text>
<text class="legal-meta">更新日期:2026年7月12日 · 生效日期:2026年7月12日</text>
<view class="legal-section">
<text class="legal-heading">引言</text>
<text class="legal-paragraph">
TKMind(以下简称「我们」)重视您的个人信息保护。本隐私政策说明我们在您使用 TKMind 微信小程序(以下简称「本小程序」)时,如何收集、使用、存储与保护您的个人信息,以及您享有的相关权利。
</text>
<text class="legal-paragraph">
在您点击同意本政策并使用登录功能前,我们不会收集您的账号、邮箱、密码或微信登录凭证。请您在使用本小程序前仔细阅读并充分理解本政策。
</text>
</view>
<view class="legal-section">
<text class="legal-heading">一、我们收集的信息</text>
<text class="legal-paragraph">
为实现账号登录与核心功能,我们将在您同意本政策后,收集以下个人信息:
</text>
<text class="legal-item">1. 账号与邮箱信息:您主动填写的用户名、邮箱地址,用于账号识别、登录验证与账号找回。</text>
<text class="legal-item">2. 登录凭证:您输入的登录密码仅用于身份验证,经加密传输,不在客户端明文保存。</text>
<text class="legal-item">3. 微信登录信息:当您选择「微信一键登录」时,我们会通过微信提供的临时登录凭证(code)换取您的微信用户标识(如 openid),用于完成身份验证与账号绑定。</text>
<text class="legal-item">4. 会话与业务数据:您与 AI 的对话内容、会话记录、MindSpace 页面访问记录,以及您主动提交的业务数据。</text>
<text class="legal-item">5. 设备与日志信息:为保障服务安全与稳定,我们可能记录设备型号、操作系统版本、网络类型、操作时间、接口请求日志等必要技术信息。</text>
</view>
<view class="legal-section">
<text class="legal-heading">二、信息的使用目的与方式</text>
<text class="legal-paragraph">我们仅在以下目的范围内使用您的个人信息:</text>
<text class="legal-item">1. 身份验证:核验您的登录身份,建立并维持您的账号会话。</text>
<text class="legal-item">2. 提供服务:向您提供 AI 对话、会话管理、MindSpace 页面浏览等核心功能。</text>
<text class="legal-item">3. 安全保障:识别异常登录、防范欺诈与滥用,保障系统与数据安全。</text>
<text class="legal-item">4. 服务改进:在符合法律法规的前提下,用于故障排查、性能优化与体验改进。</text>
<text class="legal-paragraph">
我们不会将您的个人信息用于与本政策所述目的无关的用途。如需用于其他目的,我们将另行征得您的同意。
</text>
</view>
<view class="legal-section">
<text class="legal-heading">三、信息的存储与保护</text>
<text class="legal-item">1. 您的个人信息将存储于中华人民共和国境内的安全服务器,并采取访问控制、传输加密等合理安全措施。</text>
<text class="legal-item">2. 我们仅在实现本政策所述目的所必需的期限内保留您的个人信息,法律法规另有规定的除外。</text>
<text class="legal-item">3. 如发生安全事件,我们将依法及时告知您并采取补救措施。</text>
</view>
<view class="legal-section">
<text class="legal-heading">四、信息的共享与披露</text>
<text class="legal-paragraph">
我们不会向第三方出售您的个人信息。仅在以下情形中,我们可能会共享或披露相关信息:
</text>
<text class="legal-item">1. 事先获得您的明确同意。</text>
<text class="legal-item">2. 根据法律法规、司法机关或政府主管部门的强制性要求。</text>
<text class="legal-item">3. 为履行本政策所述服务目的,向受严格保密义务约束的服务提供方(如基础设施托管方)提供必要信息。</text>
</view>
<view class="legal-section">
<text class="legal-heading">五、您的权利</text>
<text class="legal-paragraph">您依法享有以下权利:</text>
<text class="legal-item">1. 查询与访问:您可登录后查看账号基本信息及会话数据。</text>
<text class="legal-item">2. 更正与补充:如发现信息有误,可通过应用内功能或联系我们进行更正。</text>
<text class="legal-item">3. 删除与注销:您可申请删除特定数据或注销账号;注销后我们将停止提供服务并依法删除或匿名化处理相关个人信息。</text>
<text class="legal-item">4. 撤回同意:您可通过停止使用本小程序、退出登录等方式撤回授权;撤回后我们将不再处理相应个人信息,但不影响撤回前基于您同意已进行的处理。</text>
</view>
<view class="legal-section">
<text class="legal-heading">六、未成年人保护</text>
<text class="legal-paragraph">
若您为未成年人,请在监护人陪同下阅读本政策,并在取得监护人同意后使用本小程序。我们不会主动面向未满十四周岁的未成年人收集个人信息。
</text>
</view>
<view class="legal-section">
<text class="legal-heading">七、政策更新</text>
<text class="legal-paragraph">
我们可能适时修订本政策。重大变更时,我们将通过小程序内提示等方式通知您。若您继续使用本小程序,即表示您同意更新后的政策。
</text>
</view>
<view class="legal-section">
<text class="legal-heading">八、联系我们</text>
<text class="legal-paragraph">
如您对本隐私政策或个人信息处理有任何疑问、意见或投诉,请通过 TKMind 官方渠道(m.tkmind.cn)或小程序内反馈入口与我们联系,我们将在合理期限内予以答复。
</text>
</view>
</view>
</view>
+47
View File
@@ -0,0 +1,47 @@
.legal-page {
padding-bottom: 48rpx;
}
.legal-card {
display: flex;
flex-direction: column;
gap: 28rpx;
padding: 32rpx;
}
.legal-title {
font-size: 40rpx;
font-weight: 800;
color: #101820;
line-height: 1.4;
}
.legal-meta {
font-size: 24rpx;
color: #64748b;
line-height: 1.5;
}
.legal-section {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.legal-heading {
font-size: 30rpx;
font-weight: 700;
color: #132028;
line-height: 1.5;
}
.legal-paragraph,
.legal-item {
font-size: 26rpx;
color: #334155;
line-height: 1.8;
}
.legal-item {
padding-left: 8rpx;
}
+1
View File
@@ -0,0 +1 @@
Page({});
+3
View File
@@ -0,0 +1,3 @@
{
"navigationBarTitleText": "用户服务协议"
}
+92
View File
@@ -0,0 +1,92 @@
<view class="page legal-page">
<view class="panel legal-card">
<text class="legal-title">TKMind 用户服务协议</text>
<text class="legal-meta">更新日期:2026年7月12日 · 生效日期:2026年7月12日</text>
<view class="legal-section">
<text class="legal-heading">一、协议范围</text>
<text class="legal-paragraph">
本协议是您与 TKMind 运营方之间,就您使用 TKMind 微信小程序(以下简称「本小程序」)所订立的法律协议。请您在注册、登录或使用本小程序前仔细阅读本协议。您点击同意或实际使用本小程序,即视为您已阅读并同意受本协议约束。
</text>
</view>
<view class="legal-section">
<text class="legal-heading">二、服务内容</text>
<text class="legal-paragraph">本小程序向您提供包括但不限于以下服务:</text>
<text class="legal-item">1. AI 智能对话与助手服务;</text>
<text class="legal-item">2. 会话历史管理与查看;</text>
<text class="legal-item">3. MindSpace 页面浏览与预览;</text>
<text class="legal-item">4. 微信一键登录及 H5 账号登录等身份验证服务。</text>
<text class="legal-paragraph">
我们有权根据业务发展需要调整服务内容,并将通过合理方式告知您。
</text>
</view>
<view class="legal-section">
<text class="legal-heading">三、账号注册与使用</text>
<text class="legal-item">1. 您应使用真实、合法、有效的账号信息完成登录,并妥善保管账号与密码,不得将账号转让、出借或供他人使用。</text>
<text class="legal-item">2. 您应对使用本账号进行的所有操作行为负责。如发现账号被盗用或存在安全风险,请立即联系我们。</text>
<text class="legal-item">3. 使用微信一键登录时,您授权我们依据微信平台规则获取完成登录所必需的用户标识信息。</text>
</view>
<view class="legal-section">
<text class="legal-heading">四、用户行为规范</text>
<text class="legal-paragraph">您在使用本小程序时,不得从事以下行为:</text>
<text class="legal-item">1. 发布、传播违反法律法规、公序良俗或侵犯他人合法权益的内容;</text>
<text class="legal-item">2. 利用本小程序从事欺诈、骚扰、网络攻击或其他危害网络安全的行为;</text>
<text class="legal-item">3. 未经授权访问、干扰或破坏本小程序及相关系统;</text>
<text class="legal-item">4. 利用技术手段批量注册、滥用接口或干扰服务正常运行。</text>
<text class="legal-paragraph">
如您违反上述规定,我们有权采取警告、限制功能、暂停或终止服务等措施,并依法保留追究法律责任的权利。
</text>
</view>
<view class="legal-section">
<text class="legal-heading">五、个人信息保护</text>
<text class="legal-paragraph">
我们重视您的个人信息保护。关于个人信息的收集、使用、存储方式及您的权利,请详见《隐私政策》。您使用本小程序即表示您已阅读并同意《隐私政策》。
</text>
</view>
<view class="legal-section">
<text class="legal-heading">六、知识产权</text>
<text class="legal-paragraph">
本小程序的软件、界面设计、商标及相关内容的知识产权归 TKMind 运营方或相关权利人所有。未经授权,您不得复制、修改、传播或用于商业用途。
</text>
<text class="legal-paragraph">
您通过本小程序生成或上传的内容,其权利归属依照相关法律法规及您与我们的约定确定。您应确保对所提交内容拥有合法权利,不侵犯第三方权益。
</text>
</view>
<view class="legal-section">
<text class="legal-heading">七、免责声明</text>
<text class="legal-item">1. AI 生成内容仅供参考,不构成专业建议(包括但不限于医疗、法律、投资等领域建议),您应自行判断并承担使用风险。</text>
<text class="legal-item">2. 因不可抗力、网络故障、第三方服务中断或您自身原因导致的服务中断或数据损失,我们在法律允许范围内不承担责任。</text>
<text class="legal-item">3. 我们将尽力保障服务稳定,但不保证服务持续无中断、无错误或完全满足您的特定需求。</text>
</view>
<view class="legal-section">
<text class="legal-heading">八、协议变更与终止</text>
<text class="legal-paragraph">
我们有权根据法律法规或业务需要修订本协议,并通过小程序内公告等方式通知您。若您不同意变更内容,可停止使用本小程序;若您继续使用,则视为接受变更后的协议。
</text>
<text class="legal-paragraph">
您可随时停止使用本小程序。我们亦可在您严重违反本协议时终止向您提供服务。
</text>
</view>
<view class="legal-section">
<text class="legal-heading">九、适用法律与争议解决</text>
<text class="legal-paragraph">
本协议的订立、执行与解释适用中华人民共和国法律。因本协议引起的争议,双方应友好协商解决;协商不成的,任何一方可向 TKMind 运营方所在地有管辖权的人民法院提起诉讼。
</text>
</view>
<view class="legal-section">
<text class="legal-heading">十、联系我们</text>
<text class="legal-paragraph">
如您对本协议有任何疑问,请通过 TKMind 官方渠道(m.tkmind.cn)或小程序内反馈入口与我们联系。
</text>
</view>
</view>
</view>
+1
View File
@@ -0,0 +1 @@
@import '../privacy/index.wxss';
+77 -2
View File
@@ -3,9 +3,19 @@ const {
loginWithPassword,
loginWithWechatCode,
isTouristMode,
getApiBaseUrl
getApiBaseUrl,
getStoredSession
} = require('../../utils/api');
const { MINIAPP_LOGIN_PATH } = require('../../utils/config');
const {
saveLegalConsent,
shouldSkipLoginConsent
} = require('../../utils/legal-consent');
function hasStoredSessionCookie() {
const session = getStoredSession();
return Boolean(session?.cookie);
}
Page({
data: {
@@ -15,10 +25,18 @@ Page({
wechatLoading: false,
passwordLoading: false,
touristWarning: '',
envHint: ''
envHint: '',
agreedToLegal: false,
showLegalConsent: true,
showConsentModal: false
},
onLoad() {
const skipConsent = hasStoredSessionCookie() && shouldSkipLoginConsent();
this.setData({
agreedToLegal: false,
showLegalConsent: !skipConsent
});
this.refreshEnvHints();
this.tryAutoLogin();
},
@@ -40,6 +58,9 @@ Page({
},
async tryAutoLogin() {
if (!hasStoredSessionCookie()) {
return;
}
try {
const status = await checkAuth();
if (status?.authenticated) {
@@ -62,10 +83,61 @@ Page({
this.setData({ password: event.detail.value, error: '' });
},
handleConsentChange(event) {
const agreedToLegal = (event.detail.value || []).includes('agreed');
this.setData({ agreedToLegal, error: '' });
},
openTerms() {
wx.navigateTo({ url: '/pages/legal/terms/index' });
},
openPrivacy() {
wx.navigateTo({ url: '/pages/legal/privacy/index' });
},
openLegalCenter() {
wx.navigateTo({ url: '/pages/legal/index/index' });
},
ensureLegalConsent() {
if (!this.data.showLegalConsent || this.data.agreedToLegal) {
return true;
}
this.setData({
error: '请先阅读并同意《用户服务协议》和《隐私政策》后再登录',
showConsentModal: true
});
return false;
},
acceptConsentModal() {
this.setData({
agreedToLegal: true,
showConsentModal: false,
error: ''
});
},
declineConsentModal() {
this.setData({ showConsentModal: false });
wx.showToast({
title: '需同意协议后方可使用',
icon: 'none'
});
},
persistConsentAfterLogin() {
saveLegalConsent();
},
async handleWechatLogin() {
if (!this.ensureLegalConsent()) return;
this.setData({ wechatLoading: true, error: '' });
try {
await loginWithWechatCode(MINIAPP_LOGIN_PATH);
this.persistConsentAfterLogin();
wx.switchTab({ url: '/pages/chat/index' });
} catch (error) {
this.setData({
@@ -79,6 +151,8 @@ Page({
},
async handlePasswordLogin() {
if (!this.ensureLegalConsent()) return;
const username = this.data.username.trim();
const password = this.data.password;
if (!username || !password) {
@@ -88,6 +162,7 @@ Page({
this.setData({ passwordLoading: true, error: '' });
try {
await loginWithPassword(username, password);
this.persistConsentAfterLogin();
wx.switchTab({ url: '/pages/chat/index' });
} catch (error) {
this.setData({ error: error?.message || '登录失败,请重试' });
+4 -1
View File
@@ -1,3 +1,6 @@
{
"navigationBarTitleText": "登录 TKMind"
"navigationBarTitleText": "登录 TKMind",
"usingComponents": {
"privacy-popup": "/components/privacy-popup/index"
}
}
+58 -2
View File
@@ -8,7 +8,11 @@
<view wx:if="{{touristWarning}}" class="warn-box">{{touristWarning}}</view>
<view wx:if="{{envHint}}" class="info-box">{{envHint}}</view>
<button class="primary-button" loading="{{wechatLoading}}" bindtap="handleWechatLogin">
<button
class="primary-button"
loading="{{wechatLoading}}"
bindtap="handleWechatLogin"
>
微信一键登录
</button>
@@ -16,10 +20,62 @@
<input class="field" placeholder="用户名 / 邮箱" value="{{username}}" bindinput="handleUsername" />
<input class="field" placeholder="密码" password value="{{password}}" bindinput="handlePassword" />
<button class="secondary-button" loading="{{passwordLoading}}" bindtap="handlePasswordLogin">
<button
class="secondary-button"
loading="{{passwordLoading}}"
bindtap="handlePasswordLogin"
>
账号登录
</button>
<view wx:if="{{showLegalConsent}}" class="consent-box">
<checkbox-group bindchange="handleConsentChange">
<label class="consent-row">
<checkbox value="agreed" checked="{{agreedToLegal}}" color="#0f766e" />
<view class="consent-text">
<text>我已阅读并同意</text>
<text class="consent-link" catchtap="openTerms">《用户服务协议》</text>
<text>和</text>
<text class="consent-link" catchtap="openPrivacy">《隐私政策》</text>
<text>,并授权 TKMind 为账号登录、身份验证及提供 AI 对话服务,收集和使用我的用户名、邮箱、登录密码(加密传输)及微信登录标识。</text>
</view>
</label>
</checkbox-group>
</view>
<text wx:if="{{error}}" class="error">{{error}}</text>
</view>
<view class="legal-footer">
<text class="consent-link" bindtap="openTerms">用户服务协议</text>
<text class="legal-sep">|</text>
<text class="consent-link" bindtap="openPrivacy">隐私政策</text>
<text class="legal-sep">|</text>
<text class="consent-link" bindtap="openLegalCenter">隐私与协议</text>
</view>
</view>
<view wx:if="{{showConsentModal}}" class="consent-modal-mask">
<view class="consent-modal panel">
<text class="consent-modal-title">个人信息收集说明</text>
<text class="consent-modal-body">
为完成账号登录并提供 AI 对话、会话管理等服务,TKMind 将收集以下信息:用户名/邮箱、登录密码(加密传输)、微信登录标识、会话与聊天记录。
</text>
<text class="consent-modal-body">
收集目的:账号验证、身份识别、提供服务与安全保障。收集方式:由您主动填写或授权微信登录后获取。使用范围:仅用于本小程序及相关服务,不会出售给第三方。
</text>
<text class="consent-modal-body">
请阅读
<text class="consent-link" bindtap="openTerms">《用户服务协议》</text>
<text class="consent-link" bindtap="openPrivacy">《隐私政策》</text>
后勾选同意。仅在您同意后,我们才会处理相关个人信息。
</text>
<view class="consent-modal-actions">
<button class="consent-decline-button" bindtap="declineConsentModal">暂不同意</button>
<button class="consent-accept-button" bindtap="acceptConsentModal">我已阅读并同意</button>
</view>
</view>
</view>
<privacy-popup />
+95
View File
@@ -70,3 +70,98 @@
color: #1d4f91;
border: 1rpx solid #c7ddff;
}
.consent-box {
padding: 8rpx 0;
}
.consent-row {
display: flex;
align-items: flex-start;
gap: 12rpx;
}
.consent-text {
flex: 1;
font-size: 24rpx;
color: #475569;
line-height: 1.7;
}
.consent-link {
color: #0f766e;
font-weight: 600;
}
.consent-modal-mask {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx;
background: rgba(15, 23, 42, 0.55);
box-sizing: border-box;
}
.consent-modal {
display: flex;
flex-direction: column;
gap: 24rpx;
width: 100%;
max-width: 640rpx;
padding: 36rpx;
}
.consent-modal-title {
font-size: 34rpx;
font-weight: 800;
color: #101820;
}
.consent-modal-body {
font-size: 26rpx;
color: #334155;
line-height: 1.8;
}
.consent-modal-actions {
display: flex;
gap: 16rpx;
margin-top: 8rpx;
}
.consent-decline-button,
.consent-accept-button {
flex: 1;
height: 80rpx;
border-radius: 12rpx;
font-size: 28rpx;
font-weight: 700;
line-height: 80rpx;
}
.consent-decline-button {
background: #f1f5f9;
color: #475569;
}
.consent-accept-button {
background: #0f766e;
color: #ffffff;
}
.legal-footer {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 12rpx;
font-size: 24rpx;
color: #64748b;
}
.legal-sep {
color: #cbd5e1;
}
+2
View File
@@ -1,5 +1,6 @@
const SESSION_KEY = 'tkmind_session';
const { createUuid, SESSION_COOKIE_NAME } = require('./config');
const { clearLegalConsent } = require('./legal-consent');
let apiBaseUrl = '';
let cookie = '';
@@ -225,6 +226,7 @@ async function logout() {
await portalRequest('/auth/logout', { method: 'POST' });
} finally {
clearSession();
clearLegalConsent();
}
}
+44
View File
@@ -0,0 +1,44 @@
const CONSENT_KEY = 'tkmind_legal_consent';
const CONSENT_VERSION = '1.0';
function getStoredConsent() {
try {
const stored = wx.getStorageSync(CONSENT_KEY);
if (stored && typeof stored === 'object') {
return stored;
}
} catch {
// Ignore storage read errors.
}
return null;
}
function hasAcceptedLegalConsent() {
const stored = getStoredConsent();
return stored?.accepted === true && stored?.version === CONSENT_VERSION;
}
function saveLegalConsent() {
wx.setStorageSync(CONSENT_KEY, {
accepted: true,
version: CONSENT_VERSION,
acceptedAt: new Date().toISOString(),
});
}
function clearLegalConsent() {
wx.removeStorageSync(CONSENT_KEY);
}
function shouldSkipLoginConsent() {
return hasAcceptedLegalConsent();
}
module.exports = {
CONSENT_VERSION,
getStoredConsent,
hasAcceptedLegalConsent,
saveLegalConsent,
clearLegalConsent,
shouldSkipLoginConsent,
};
+51
View File
@@ -0,0 +1,51 @@
function canUsePrivacyApis() {
return typeof wx.getPrivacySetting === 'function';
}
function getPrivacySetting() {
return new Promise((resolve) => {
if (!canUsePrivacyApis()) {
resolve({ needAuthorization: false, privacyContractName: '' });
return;
}
wx.getPrivacySetting({
success(res) {
resolve({
needAuthorization: Boolean(res?.needAuthorization),
privacyContractName: String(res?.privacyContractName || ''),
});
},
fail() {
resolve({ needAuthorization: false, privacyContractName: '' });
},
});
});
}
function openPrivacyContract() {
if (typeof wx.openPrivacyContract !== 'function') {
wx.showToast({ title: '请升级微信版本后查看', icon: 'none' });
return;
}
wx.openPrivacyContract({
fail() {
wx.showToast({ title: '暂时无法打开隐私指引', icon: 'none' });
},
});
}
function registerPrivacyAuthorizationListener(handler) {
if (typeof wx.onNeedPrivacyAuthorization !== 'function') {
return;
}
wx.onNeedPrivacyAuthorization((resolve) => {
handler(resolve);
});
}
module.exports = {
canUsePrivacyApis,
getPrivacySetting,
openPrivacyContract,
registerPrivacyAuthorizationListener,
};