diff --git a/miniapp/README.md b/miniapp/README.md index d348b10..8a856d8 100644 --- a/miniapp/README.md +++ b/miniapp/README.md @@ -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`;未部署前可先用账号密码登录。 ### 开发者工具仍显示「游客模式」时 diff --git a/miniapp/app.js b/miniapp/app.js index 5d1d322..1af1ff1 100644 --- a/miniapp/app.js +++ b/miniapp/app.js @@ -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(); }, }); diff --git a/miniapp/app.json b/miniapp/app.json index 7c7a7bc..91cd042 100644 --- a/miniapp/app.json +++ b/miniapp/app.json @@ -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", diff --git a/miniapp/components/privacy-popup/index.js b/miniapp/components/privacy-popup/index.js new file mode 100644 index 0000000..4e49fdc --- /dev/null +++ b/miniapp/components/privacy-popup/index.js @@ -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; + } + }, + }, +}); diff --git a/miniapp/components/privacy-popup/index.json b/miniapp/components/privacy-popup/index.json new file mode 100644 index 0000000..a89ef4d --- /dev/null +++ b/miniapp/components/privacy-popup/index.json @@ -0,0 +1,4 @@ +{ + "component": true, + "usingComponents": {} +} diff --git a/miniapp/components/privacy-popup/index.wxml b/miniapp/components/privacy-popup/index.wxml new file mode 100644 index 0000000..195c5b1 --- /dev/null +++ b/miniapp/components/privacy-popup/index.wxml @@ -0,0 +1,21 @@ + + + 用户隐私保护提示 + + 在使用 TKMind 前,请阅读 + {{privacyContractName}} + 。点击「同意」即表示你已阅读并同意该指引。 + + + + + + + diff --git a/miniapp/components/privacy-popup/index.wxss b/miniapp/components/privacy-popup/index.wxss new file mode 100644 index 0000000..90d4001 --- /dev/null +++ b/miniapp/components/privacy-popup/index.wxss @@ -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; +} diff --git a/miniapp/pages/chat/index.js b/miniapp/pages/chat/index.js index ca69ce9..3058eec 100644 --- a/miniapp/pages/chat/index.js +++ b/miniapp/pages/chat/index.js @@ -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) { diff --git a/miniapp/pages/chat/index.json b/miniapp/pages/chat/index.json index 29fc0ed..e5d7f40 100644 --- a/miniapp/pages/chat/index.json +++ b/miniapp/pages/chat/index.json @@ -1,3 +1,6 @@ { - "navigationBarTitleText": "TKMind 聊天" + "navigationBarTitleText": "TKMind 聊天", + "usingComponents": { + "privacy-popup": "/components/privacy-popup/index" + } } diff --git a/miniapp/pages/chat/index.wxml b/miniapp/pages/chat/index.wxml index 1d7c22f..6d7c68b 100644 --- a/miniapp/pages/chat/index.wxml +++ b/miniapp/pages/chat/index.wxml @@ -6,6 +6,7 @@ + @@ -71,3 +72,5 @@ + + diff --git a/miniapp/pages/legal/index/index.js b/miniapp/pages/legal/index/index.js new file mode 100644 index 0000000..d4c97fe --- /dev/null +++ b/miniapp/pages/legal/index/index.js @@ -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(); + }, +}); diff --git a/miniapp/pages/legal/index/index.json b/miniapp/pages/legal/index/index.json new file mode 100644 index 0000000..9cadb0d --- /dev/null +++ b/miniapp/pages/legal/index/index.json @@ -0,0 +1,3 @@ +{ + "navigationBarTitleText": "隐私与协议" +} diff --git a/miniapp/pages/legal/index/index.wxml b/miniapp/pages/legal/index/index.wxml new file mode 100644 index 0000000..104961e --- /dev/null +++ b/miniapp/pages/legal/index/index.wxml @@ -0,0 +1,23 @@ + + + 隐私与协议 + + 你可以随时查看 TKMind 的用户服务协议与隐私政策,了解我们如何收集、使用与保护你的个人信息。 + + + + 用户服务协议 + 服务范围、账号使用规范与免责声明 + + + + 隐私政策 + 个人信息收集目的、方式、用途与您的权利 + + + + 微信用户隐私保护指引 + 查看微信平台提供的隐私保护说明 + + + diff --git a/miniapp/pages/legal/index/index.wxss b/miniapp/pages/legal/index/index.wxss new file mode 100644 index 0000000..2f0187b --- /dev/null +++ b/miniapp/pages/legal/index/index.wxss @@ -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; +} diff --git a/miniapp/pages/legal/privacy/index.js b/miniapp/pages/legal/privacy/index.js new file mode 100644 index 0000000..560d44d --- /dev/null +++ b/miniapp/pages/legal/privacy/index.js @@ -0,0 +1 @@ +Page({}); diff --git a/miniapp/pages/legal/privacy/index.json b/miniapp/pages/legal/privacy/index.json new file mode 100644 index 0000000..d5e2779 --- /dev/null +++ b/miniapp/pages/legal/privacy/index.json @@ -0,0 +1,3 @@ +{ + "navigationBarTitleText": "隐私政策" +} diff --git a/miniapp/pages/legal/privacy/index.wxml b/miniapp/pages/legal/privacy/index.wxml new file mode 100644 index 0000000..f29c711 --- /dev/null +++ b/miniapp/pages/legal/privacy/index.wxml @@ -0,0 +1,87 @@ + + + TKMind 隐私政策 + 更新日期:2026年7月12日 · 生效日期:2026年7月12日 + + + 引言 + + TKMind(以下简称「我们」)重视您的个人信息保护。本隐私政策说明我们在您使用 TKMind 微信小程序(以下简称「本小程序」)时,如何收集、使用、存储与保护您的个人信息,以及您享有的相关权利。 + + + 在您点击同意本政策并使用登录功能前,我们不会收集您的账号、邮箱、密码或微信登录凭证。请您在使用本小程序前仔细阅读并充分理解本政策。 + + + + + 一、我们收集的信息 + + 为实现账号登录与核心功能,我们将在您同意本政策后,收集以下个人信息: + + 1. 账号与邮箱信息:您主动填写的用户名、邮箱地址,用于账号识别、登录验证与账号找回。 + 2. 登录凭证:您输入的登录密码仅用于身份验证,经加密传输,不在客户端明文保存。 + 3. 微信登录信息:当您选择「微信一键登录」时,我们会通过微信提供的临时登录凭证(code)换取您的微信用户标识(如 openid),用于完成身份验证与账号绑定。 + 4. 会话与业务数据:您与 AI 的对话内容、会话记录、MindSpace 页面访问记录,以及您主动提交的业务数据。 + 5. 设备与日志信息:为保障服务安全与稳定,我们可能记录设备型号、操作系统版本、网络类型、操作时间、接口请求日志等必要技术信息。 + + + + 二、信息的使用目的与方式 + 我们仅在以下目的范围内使用您的个人信息: + 1. 身份验证:核验您的登录身份,建立并维持您的账号会话。 + 2. 提供服务:向您提供 AI 对话、会话管理、MindSpace 页面浏览等核心功能。 + 3. 安全保障:识别异常登录、防范欺诈与滥用,保障系统与数据安全。 + 4. 服务改进:在符合法律法规的前提下,用于故障排查、性能优化与体验改进。 + + 我们不会将您的个人信息用于与本政策所述目的无关的用途。如需用于其他目的,我们将另行征得您的同意。 + + + + + 三、信息的存储与保护 + 1. 您的个人信息将存储于中华人民共和国境内的安全服务器,并采取访问控制、传输加密等合理安全措施。 + 2. 我们仅在实现本政策所述目的所必需的期限内保留您的个人信息,法律法规另有规定的除外。 + 3. 如发生安全事件,我们将依法及时告知您并采取补救措施。 + + + + 四、信息的共享与披露 + + 我们不会向第三方出售您的个人信息。仅在以下情形中,我们可能会共享或披露相关信息: + + 1. 事先获得您的明确同意。 + 2. 根据法律法规、司法机关或政府主管部门的强制性要求。 + 3. 为履行本政策所述服务目的,向受严格保密义务约束的服务提供方(如基础设施托管方)提供必要信息。 + + + + 五、您的权利 + 您依法享有以下权利: + 1. 查询与访问:您可登录后查看账号基本信息及会话数据。 + 2. 更正与补充:如发现信息有误,可通过应用内功能或联系我们进行更正。 + 3. 删除与注销:您可申请删除特定数据或注销账号;注销后我们将停止提供服务并依法删除或匿名化处理相关个人信息。 + 4. 撤回同意:您可通过停止使用本小程序、退出登录等方式撤回授权;撤回后我们将不再处理相应个人信息,但不影响撤回前基于您同意已进行的处理。 + + + + 六、未成年人保护 + + 若您为未成年人,请在监护人陪同下阅读本政策,并在取得监护人同意后使用本小程序。我们不会主动面向未满十四周岁的未成年人收集个人信息。 + + + + + 七、政策更新 + + 我们可能适时修订本政策。重大变更时,我们将通过小程序内提示等方式通知您。若您继续使用本小程序,即表示您同意更新后的政策。 + + + + + 八、联系我们 + + 如您对本隐私政策或个人信息处理有任何疑问、意见或投诉,请通过 TKMind 官方渠道(m.tkmind.cn)或小程序内反馈入口与我们联系,我们将在合理期限内予以答复。 + + + + diff --git a/miniapp/pages/legal/privacy/index.wxss b/miniapp/pages/legal/privacy/index.wxss new file mode 100644 index 0000000..4fd349c --- /dev/null +++ b/miniapp/pages/legal/privacy/index.wxss @@ -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; +} diff --git a/miniapp/pages/legal/terms/index.js b/miniapp/pages/legal/terms/index.js new file mode 100644 index 0000000..560d44d --- /dev/null +++ b/miniapp/pages/legal/terms/index.js @@ -0,0 +1 @@ +Page({}); diff --git a/miniapp/pages/legal/terms/index.json b/miniapp/pages/legal/terms/index.json new file mode 100644 index 0000000..3c98e8d --- /dev/null +++ b/miniapp/pages/legal/terms/index.json @@ -0,0 +1,3 @@ +{ + "navigationBarTitleText": "用户服务协议" +} diff --git a/miniapp/pages/legal/terms/index.wxml b/miniapp/pages/legal/terms/index.wxml new file mode 100644 index 0000000..470ff22 --- /dev/null +++ b/miniapp/pages/legal/terms/index.wxml @@ -0,0 +1,92 @@ + + + TKMind 用户服务协议 + 更新日期:2026年7月12日 · 生效日期:2026年7月12日 + + + 一、协议范围 + + 本协议是您与 TKMind 运营方之间,就您使用 TKMind 微信小程序(以下简称「本小程序」)所订立的法律协议。请您在注册、登录或使用本小程序前仔细阅读本协议。您点击同意或实际使用本小程序,即视为您已阅读并同意受本协议约束。 + + + + + 二、服务内容 + 本小程序向您提供包括但不限于以下服务: + 1. AI 智能对话与助手服务; + 2. 会话历史管理与查看; + 3. MindSpace 页面浏览与预览; + 4. 微信一键登录及 H5 账号登录等身份验证服务。 + + 我们有权根据业务发展需要调整服务内容,并将通过合理方式告知您。 + + + + + 三、账号注册与使用 + 1. 您应使用真实、合法、有效的账号信息完成登录,并妥善保管账号与密码,不得将账号转让、出借或供他人使用。 + 2. 您应对使用本账号进行的所有操作行为负责。如发现账号被盗用或存在安全风险,请立即联系我们。 + 3. 使用微信一键登录时,您授权我们依据微信平台规则获取完成登录所必需的用户标识信息。 + + + + 四、用户行为规范 + 您在使用本小程序时,不得从事以下行为: + 1. 发布、传播违反法律法规、公序良俗或侵犯他人合法权益的内容; + 2. 利用本小程序从事欺诈、骚扰、网络攻击或其他危害网络安全的行为; + 3. 未经授权访问、干扰或破坏本小程序及相关系统; + 4. 利用技术手段批量注册、滥用接口或干扰服务正常运行。 + + 如您违反上述规定,我们有权采取警告、限制功能、暂停或终止服务等措施,并依法保留追究法律责任的权利。 + + + + + 五、个人信息保护 + + 我们重视您的个人信息保护。关于个人信息的收集、使用、存储方式及您的权利,请详见《隐私政策》。您使用本小程序即表示您已阅读并同意《隐私政策》。 + + + + + 六、知识产权 + + 本小程序的软件、界面设计、商标及相关内容的知识产权归 TKMind 运营方或相关权利人所有。未经授权,您不得复制、修改、传播或用于商业用途。 + + + 您通过本小程序生成或上传的内容,其权利归属依照相关法律法规及您与我们的约定确定。您应确保对所提交内容拥有合法权利,不侵犯第三方权益。 + + + + + 七、免责声明 + 1. AI 生成内容仅供参考,不构成专业建议(包括但不限于医疗、法律、投资等领域建议),您应自行判断并承担使用风险。 + 2. 因不可抗力、网络故障、第三方服务中断或您自身原因导致的服务中断或数据损失,我们在法律允许范围内不承担责任。 + 3. 我们将尽力保障服务稳定,但不保证服务持续无中断、无错误或完全满足您的特定需求。 + + + + 八、协议变更与终止 + + 我们有权根据法律法规或业务需要修订本协议,并通过小程序内公告等方式通知您。若您不同意变更内容,可停止使用本小程序;若您继续使用,则视为接受变更后的协议。 + + + 您可随时停止使用本小程序。我们亦可在您严重违反本协议时终止向您提供服务。 + + + + + 九、适用法律与争议解决 + + 本协议的订立、执行与解释适用中华人民共和国法律。因本协议引起的争议,双方应友好协商解决;协商不成的,任何一方可向 TKMind 运营方所在地有管辖权的人民法院提起诉讼。 + + + + + 十、联系我们 + + 如您对本协议有任何疑问,请通过 TKMind 官方渠道(m.tkmind.cn)或小程序内反馈入口与我们联系。 + + + + diff --git a/miniapp/pages/legal/terms/index.wxss b/miniapp/pages/legal/terms/index.wxss new file mode 100644 index 0000000..53a90fa --- /dev/null +++ b/miniapp/pages/legal/terms/index.wxss @@ -0,0 +1 @@ +@import '../privacy/index.wxss'; diff --git a/miniapp/pages/login/index.js b/miniapp/pages/login/index.js index 363d926..9405319 100644 --- a/miniapp/pages/login/index.js +++ b/miniapp/pages/login/index.js @@ -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 || '登录失败,请重试' }); diff --git a/miniapp/pages/login/index.json b/miniapp/pages/login/index.json index 261164b..581a47c 100644 --- a/miniapp/pages/login/index.json +++ b/miniapp/pages/login/index.json @@ -1,3 +1,6 @@ { - "navigationBarTitleText": "登录 TKMind" + "navigationBarTitleText": "登录 TKMind", + "usingComponents": { + "privacy-popup": "/components/privacy-popup/index" + } } diff --git a/miniapp/pages/login/index.wxml b/miniapp/pages/login/index.wxml index 03b6bbc..dd80273 100644 --- a/miniapp/pages/login/index.wxml +++ b/miniapp/pages/login/index.wxml @@ -8,7 +8,11 @@ {{touristWarning}} {{envHint}} - @@ -16,10 +20,62 @@ - + + + + + + {{error}} + + + 用户服务协议 + | + 隐私政策 + | + 隐私与协议 + + + + + 个人信息收集说明 + + 为完成账号登录并提供 AI 对话、会话管理等服务,TKMind 将收集以下信息:用户名/邮箱、登录密码(加密传输)、微信登录标识、会话与聊天记录。 + + + 收集目的:账号验证、身份识别、提供服务与安全保障。收集方式:由您主动填写或授权微信登录后获取。使用范围:仅用于本小程序及相关服务,不会出售给第三方。 + + + 请阅读 + 《用户服务协议》 + 和 + 《隐私政策》 + 后勾选同意。仅在您同意后,我们才会处理相关个人信息。 + + + + + + + + + diff --git a/miniapp/pages/login/index.wxss b/miniapp/pages/login/index.wxss index a3c1be2..b43688e 100644 --- a/miniapp/pages/login/index.wxss +++ b/miniapp/pages/login/index.wxss @@ -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; +} diff --git a/miniapp/utils/api.js b/miniapp/utils/api.js index 3bb6cd0..83a3768 100644 --- a/miniapp/utils/api.js +++ b/miniapp/utils/api.js @@ -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(); } } diff --git a/miniapp/utils/legal-consent.js b/miniapp/utils/legal-consent.js new file mode 100644 index 0000000..9f1fba2 --- /dev/null +++ b/miniapp/utils/legal-consent.js @@ -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, +}; diff --git a/miniapp/utils/privacy.js b/miniapp/utils/privacy.js new file mode 100644 index 0000000..2f7e439 --- /dev/null +++ b/miniapp/utils/privacy.js @@ -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, +};