From 98c45f2c1ea1814863c965bad6657b471c343184 Mon Sep 17 00:00:00 2001 From: zhengchiliu <857493511@qq.com> Date: Wed, 16 Sep 2026 02:45:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=8D=8F=E8=AE=AE):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=8D=8F=E8=AE=AE=E4=B8=8E=E9=9A=90=E7=A7=81?= =?UTF-8?q?=E6=94=BF=E7=AD=96=E9=A1=B5=E9=9D=A2=E5=8F=8A=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniprogram/app.json | 3 +- miniprogram/pages/agreement/agreement.json | 5 + miniprogram/pages/agreement/agreement.ts | 49 ++ miniprogram/pages/agreement/agreement.wxml | 57 ++ miniprogram/pages/agreement/agreement.wxss | 153 ++++++ miniprogram/pages/login/login.ts | 13 +- miniprogram/pages/login/login.wxml | 4 +- .../pages/notification/notification.ts | 43 +- miniprogram/pages/profile/profile.ts | 14 + miniprogram/pages/profile/profile.wxml | 10 + miniprogram/utils/legal.ts | 497 ++++++++++++++++++ 11 files changed, 827 insertions(+), 21 deletions(-) create mode 100644 miniprogram/pages/agreement/agreement.json create mode 100644 miniprogram/pages/agreement/agreement.ts create mode 100644 miniprogram/pages/agreement/agreement.wxml create mode 100644 miniprogram/pages/agreement/agreement.wxss create mode 100644 miniprogram/utils/legal.ts diff --git a/miniprogram/app.json b/miniprogram/app.json index 9fab9c7..5c5e173 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -18,7 +18,8 @@ "pages/closet-share-detail/closet-share-detail", "pages/outfit-share-detail/outfit-share-detail", "pages/mall/mall", - "pages/order/order" + "pages/order/order", + "pages/agreement/agreement" ], "tabBar": { "custom": true, diff --git a/miniprogram/pages/agreement/agreement.json b/miniprogram/pages/agreement/agreement.json new file mode 100644 index 0000000..8632c9b --- /dev/null +++ b/miniprogram/pages/agreement/agreement.json @@ -0,0 +1,5 @@ +{ + "usingComponents": { + "navigation-bar": "/components/navigation-bar/navigation-bar" + } +} diff --git a/miniprogram/pages/agreement/agreement.ts b/miniprogram/pages/agreement/agreement.ts new file mode 100644 index 0000000..05d2d4a --- /dev/null +++ b/miniprogram/pages/agreement/agreement.ts @@ -0,0 +1,49 @@ +import { LEGAL_DOCS, LEGAL_META, LegalDoc } from '../../utils/legal' +import { navScroll } from '../../utils/nav' +import { logger } from '../../utils/logger' + +// 日志模块名 +const LOG = 'agreement' + +type LegalType = 'user' | 'privacy' + +/** + * 协议页(《用户协议》/《隐私政策》共用) + * + * 入口:我的 → 设置 → 用户协议 / 隐私政策;登录页底部协议链接。 + * 通过 query.type 区分文档,页面内可一键切换到另一份,保证随时可查阅。 + * 本页不发起任何网络请求,未登录状态同样可访问。 + */ +Page({ + data: { + type: 'user' as LegalType, + doc: LEGAL_DOCS.user as LegalDoc, + meta: LEGAL_META, + tabs: [ + { key: 'user', label: '用户协议' }, + { key: 'privacy', label: '隐私政策' }, + ], + // 滚动收缩:由 utils/nav.ts 的共享 handler 驱动 + navScrolled: false, + }, + + // scroll-view 滚动 → 导航收缩为玻璃小标题 + onNavScroll: navScroll, + + onLoad(query: Record) { + const type: LegalType = query && query.type === 'privacy' ? 'privacy' : 'user' + this.setData({ type, doc: LEGAL_DOCS[type] }) + logger.debug(LOG, '协议页加载, type=', type) + }, + + onSwitchTab(e: WechatMiniprogram.TouchEvent) { + const type = (e.currentTarget.dataset as { type?: LegalType }).type + if (!type || type === this.data.type) return + logger.debug(LOG, '切换协议, type=', type) + // 用 redirectTo 重建页面:天然回到顶部,且不增加页面栈(避免返回时回到上一份协议) + wx.redirectTo({ + url: `/pages/agreement/agreement?type=${type}`, + fail: (err) => logger.warn(LOG, '切换协议失败, err=', err && err.errMsg), + }) + }, +}) diff --git a/miniprogram/pages/agreement/agreement.wxml b/miniprogram/pages/agreement/agreement.wxml new file mode 100644 index 0000000..f1283af --- /dev/null +++ b/miniprogram/pages/agreement/agreement.wxml @@ -0,0 +1,57 @@ + + + + + + + {{doc.enTitle}} + {{doc.title}} + + 生效日期 {{doc.effectiveAt}} + · + 更新日期 {{doc.updatedAt}} + + + + + + {{item.label}} + + + + + + {{item}} + + + + + + {{si + 1}} + {{item.title}} + + + {{b.c}} + + + {{b.c}} + + {{b.c}} + + + + + + {{meta.operatorName}} + · {{meta.filingNo}} + + + + + diff --git a/miniprogram/pages/agreement/agreement.wxss b/miniprogram/pages/agreement/agreement.wxss new file mode 100644 index 0000000..81f7278 --- /dev/null +++ b/miniprogram/pages/agreement/agreement.wxss @@ -0,0 +1,153 @@ +/** 协议页:杂志内页排版 —— 衬线大标题 + 条目编号 + 竖排编号长文 **/ +.page-flex { + height: 100vh; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.scroll-body { + flex: 1; + height: 0; +} + +.ag-title { + margin-top: 18rpx; +} + +.ag-meta { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 12rpx; + margin-top: 24rpx; + font-size: 22rpx; + color: var(--ink-3); + letter-spacing: .02em; + font-variant-numeric: tabular-nums; +} + +.ag-meta-sep { + color: var(--ink-4); +} + +/* 协议互切:下划线式极简分段 */ +.ag-tabs { + display: flex; + gap: 40rpx; + padding: 48rpx var(--pad) 0; +} + +.ag-tab { + position: relative; + padding-bottom: 16rpx; + font-size: 28rpx; + color: var(--ink-3); + transition: color .2s var(--ease); +} + +.ag-tab.is-on { + color: var(--ink); + font-weight: 500; +} + +.ag-tab.is-on::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 3rpx; + border-radius: 2rpx; + background: var(--ink); +} + +.ag-body { + padding: 44rpx var(--pad) 0; +} + +/* 导语 */ +.ag-lead { + padding-bottom: 12rpx; + border-bottom: 1rpx solid var(--hairline); +} + +/* 正文段落 */ +.ag-p { + display: block; + font-size: 28rpx; + line-height: 1.9; + color: var(--ink-2); + margin-bottom: 20rpx; +} + +/* 条目 */ +.ag-sec { + padding-top: 48rpx; +} + +.ag-hd { + display: flex; + align-items: baseline; + gap: 18rpx; + margin-bottom: 24rpx; +} + +.ag-no { + font-family: var(--serif); + font-size: 30rpx; + font-weight: 500; + color: var(--ink-4); + min-width: 34rpx; +} + +.ag-hd-t { + flex: 1; + font-family: var(--serif); + font-size: 36rpx; + font-weight: 500; + letter-spacing: -.005em; + line-height: 1.35; + color: var(--ink); +} + +/* 条目内小标题(如「1. 微信授权登录信息」) */ +.ag-sub { + font-size: 29rpx; + font-weight: 600; + line-height: 1.5; + color: var(--ink); + margin: 28rpx 0 14rpx; +} + +.ag-sec .ag-sub:first-child { + margin-top: 0; +} + +/* 列表项:发丝级圆点 + 悬挂缩进 */ +.ag-li { + display: flex; + align-items: flex-start; + gap: 16rpx; + margin-bottom: 18rpx; +} + +.ag-bullet { + width: 8rpx; + height: 8rpx; + border-radius: 50%; + background: var(--ink-4); + margin-top: 22rpx; + flex-shrink: 0; +} + +.ag-li-t { + flex: 1; + font-size: 28rpx; + line-height: 1.9; + color: var(--ink-2); +} + +.ag-foot { + padding-top: 64rpx; +} diff --git a/miniprogram/pages/login/login.ts b/miniprogram/pages/login/login.ts index f8fb88b..e275e90 100644 --- a/miniprogram/pages/login/login.ts +++ b/miniprogram/pages/login/login.ts @@ -212,7 +212,16 @@ Page({ }) }, - openAgreement() { - wx.showToast({ title: '协议内容敬请期待', icon: 'none' }) + // 登录页协议链接:按 data-type 打开《用户协议》或《隐私政策》 + openAgreement(e: WechatMiniprogram.TouchEvent) { + const type = + (e.currentTarget.dataset as { type?: 'user' | 'privacy' }).type === 'privacy' + ? 'privacy' + : 'user' + logger.debug(LOG, '打开协议, type=', type) + wx.navigateTo({ + url: `/pages/agreement/agreement?type=${type}`, + fail: (err) => logger.warn(LOG, 'navigateTo 协议页失败, err=', err && err.errMsg), + }) }, }) diff --git a/miniprogram/pages/login/login.wxml b/miniprogram/pages/login/login.wxml index c0b153a..944d38c 100644 --- a/miniprogram/pages/login/login.wxml +++ b/miniprogram/pages/login/login.wxml @@ -25,9 +25,9 @@ 登录即代表同意 - 《用户协议》 + 《用户协议》 和 - 《隐私政策》 + 《隐私政策》 diff --git a/miniprogram/pages/notification/notification.ts b/miniprogram/pages/notification/notification.ts index bb94afb..c0c049f 100644 --- a/miniprogram/pages/notification/notification.ts +++ b/miniprogram/pages/notification/notification.ts @@ -42,31 +42,42 @@ Page({ redirectToLogin() return } - this.refresh() - this.fetchUnread() + // 先拉列表、再全部标记已读:本次进入仍保留未读标记,让用户看清哪些是新消息; + // 服务端状态与红点在列表渲染后清零,下次进入即为已读样式。 + this.refresh().then(() => this.markAllRead()) }, - fetchUnread() { - request<{ unread_count: number }>({ - url: '/notification/unread', + // 全部标记已读:接口顺带返回最新未读数,直接用它刷新红点,省掉一次 /notification/unread + markAllRead(): Promise { + return request<{ unread_count: number }>({ + url: '/notification/read-all', + method: 'POST', showError: false, silentAuth: true, }) .then((res) => { - logger.debug(LOG, '未读数加载成功, unread=', res.unread_count) const unread = res.unread_count || 0 - wx.setStorageSync(STORAGE_KEYS.UNREAD, unread) - // 本页不是 tab 页,getTabBar() 恒为 undefined,需从页面栈里取 tab 页的 tabBar - const tabBar = getTabBarInstance() - // 直接更新红点,避免 tabBar 内部再发一次请求 - if (tabBar && tabBar.setUnread) tabBar.setUnread(unread) + logger.debug(LOG, '全部标记已读成功, unread=', unread) + this.syncUnread(unread) + }) + .catch((e: any) => { + // 失败不阻塞列表展示,红点维持原状,等下次进入再补 + logger.warn(LOG, '全部标记已读失败(静默降级), code=', e && e.code) }) - .catch((e: any) => logger.warn(LOG, '未读数加载失败(静默降级), code=', e && e.code)) }, - refresh() { + // 同步未读红点:本页不是 tab 页,getTabBar() 恒为 undefined,需从页面栈里取 tab 页的 tabBar + syncUnread(unread: number) { + wx.setStorageSync(STORAGE_KEYS.UNREAD, unread) + const tabBar = getTabBarInstance() + // 直接更新红点,避免 tabBar 内部再发一次请求 + if (tabBar && tabBar.setUnread) tabBar.setUnread(unread) + }, + + // 返回列表请求的 Promise:onShow 需要等首页数据渲染完再标记全部已读 + refresh(): Promise { this.setData({ page: 1, hasMore: true, loading: this.data.list.length === 0 }) - this.fetchList(1) + return this.fetchList(1) }, onReachBottom() { @@ -74,7 +85,7 @@ Page({ this.fetchList(this.data.page + 1) }, - fetchList(page: number) { + fetchList(page: number): Promise { const params: Record = { page, page_size: this.data.pageSize } const filter = this.data.activeFilter if (filter > 0) params.msg_type = filter @@ -84,7 +95,7 @@ Page({ ;(this as any).reqSeq = seq if (page > 1) this.setData({ loadingMore: true }) - request>({ url: '/notification', data: params }) + return request>({ url: '/notification', data: params }) .then((res) => { if ((this as any).reqSeq !== seq) return const { list: raw, total } = normalizePaged(res) diff --git a/miniprogram/pages/profile/profile.ts b/miniprogram/pages/profile/profile.ts index 4b65bb8..f1748a9 100644 --- a/miniprogram/pages/profile/profile.ts +++ b/miniprogram/pages/profile/profile.ts @@ -320,4 +320,18 @@ Page({ onTapAbout() { wx.showToast({ title: '纸条AI衣橱 v1.0.0', icon: 'none' }) }, + // 协议入口常驻设置分组,随时可查阅(协议页无网络请求,登录态异常也可打开) + onTapUserAgreement() { + this.openAgreement('user') + }, + onTapPrivacy() { + this.openAgreement('privacy') + }, + openAgreement(type: 'user' | 'privacy') { + logger.debug(LOG, '打开协议, type=', type) + wx.navigateTo({ + url: `/pages/agreement/agreement?type=${type}`, + fail: (e) => logger.warn(LOG, 'navigateTo 协议页失败, err=', e && e.errMsg), + }) + }, }) diff --git a/miniprogram/pages/profile/profile.wxml b/miniprogram/pages/profile/profile.wxml index b41b091..a67a4f0 100644 --- a/miniprogram/pages/profile/profile.wxml +++ b/miniprogram/pages/profile/profile.wxml @@ -101,6 +101,16 @@ 关于纸条AI衣橱 + + + 用户协议 + + + + + 隐私政策 + + v1.0.0 diff --git a/miniprogram/utils/legal.ts b/miniprogram/utils/legal.ts new file mode 100644 index 0000000..5703dc7 --- /dev/null +++ b/miniprogram/utils/legal.ts @@ -0,0 +1,497 @@ +/** + * 《用户协议》《隐私政策》文案与主体信息(唯一数据源) + * + * ⚠️ 上线前必须替换 LEGAL_META 中带【】的占位信息,并保证与微信公众平台 + * 「设置 → 基本设置 / 服务内容声明 → 用户隐私保护指引」中填写的内容一致。 + * 协议正文中的【】同样是待确认项(如退款处理时限、管辖法院所在地)。 + */ + +export const LEGAL_META = { + appName: '纸条AI衣橱', + operatorName: '【请填写开发者主体全称】', + contactEmail: '【请填写联系邮箱】', + contactPhone: '【请填写联系电话】', + officerEmail: '【请填写个人信息保护负责人邮箱】', + filingNo: '【请填写小程序备案号】', + effectiveAt: '2026年9月16日', + updatedAt: '2026年9月16日', +} + +export type LegalBlockType = 'p' | 'h' | 'li' + +export interface LegalBlock { + t: LegalBlockType + c: string +} + +export interface LegalSection { + title: string + blocks: LegalBlock[] +} + +export interface LegalDoc { + key: 'user' | 'privacy' + title: string + enTitle: string + effectiveAt: string + updatedAt: string + intro: string[] + sections: LegalSection[] +} + +// 短写工具:p=正文段落,h=条目小标题,li=列表项 +const p = (c: string): LegalBlock => ({ t: 'p', c }) +const h = (c: string): LegalBlock => ({ t: 'h', c }) +const li = (c: string): LegalBlock => ({ t: 'li', c }) + +const USER_SECTIONS: LegalSection[] = [ + { + title: '协议的确认与接受', + blocks: [ + p( + `本协议是你与${LEGAL_META.operatorName}(以下简称"我们")之间就使用"${LEGAL_META.appName}"微信小程序(以下简称"本小程序")及相关服务(以下简称"本服务")所订立的协议。`, + ), + p( + '你在勾选同意、点击"微信一键登录"或以任何方式开始使用本服务前,应完整、仔细地阅读本协议全部内容,特别是以【】标注或以加粗方式提示的免责、责任限制、违约处理等条款。你开始使用本服务,即视为你已充分阅读、理解并同意本协议全部内容。', + ), + p('如你不同意本协议的任何内容,请立即停止使用本服务。'), + ], + }, + { + title: '定义', + blocks: [ + li( + '本服务:指我们通过本小程序向你提供的衣物图片识别与抠图、电子衣橱管理、AI 穿搭推荐、星座运势、内容分享、额度与会员等各项功能与服务。', + ), + li( + '用户内容:指你通过本服务上传、发布、提交或以其他方式提供的信息,包括但不限于你拍摄或选择的衣物照片、穿搭信息、昵称、头像、性别、生日及反馈内容。', + ), + li( + '虚拟权益:指上传额度、AI 推荐额度、社区分享额度、衣柜分享额度、会员权益等,仅可在本服务内按规则使用,不具备货币属性,不可转让、不可兑换现金。', + ), + ], + }, + { + title: '账号与登录', + blocks: [ + p( + '本服务通过微信授权登录,你无需单独注册账号,使用微信登录后我们会为你创建与本服务对应的账号,并据此保存你的衣橱、穿搭、额度与订单等数据。', + ), + p( + '你的账号仅供你本人使用,不得转让、出借、出租、售卖或以任何方式许可他人使用。你应妥善保管微信账号及登录凭证,因你保管不善造成的损失由你自行承担。', + ), + p( + '如你发现账号被他人非法使用或存在其他安全风险,应立即通过本协议第十五条的方式联系我们。经核实存在违规行为的,我们有权依据本协议对你的账号采取限制功能、临时禁用或永久禁用等措施。', + ), + p( + '你可以通过本协议第十五条的联系方式申请注销账号。注销后账号内的衣橱数据、穿搭、额度、会员权益及订单相关服务将无法恢复,请你谨慎决定。', + ), + ], + }, + { + title: '服务内容与使用规则', + blocks: [ + h('1. 电子衣橱与衣物识别'), + li( + '你上传衣物照片后,本服务将通过 AI 完成抠图与衣物属性识别(如品类、颜色、风格、厚薄等),并为你生成电子衣橱。识别结果为算法自动生成,可能存在偏差,你可以手动修改相关属性。', + ), + h('2. AI 穿搭推荐'), + li( + '本服务可根据你的衣橱与所选场景生成穿搭建议。推荐结果由算法生成,仅供你穿搭参考,不构成任何专业建议,我们不对其风格适配性、场合适宜性作出承诺。', + ), + h('3. 星座运势'), + li( + '本服务根据你填写的生日信息展示星座运势内容,该内容仅供娱乐参考,不构成任何决策依据。', + ), + h('4. 分享与社区'), + li( + '你可以将衣橱或穿搭生成为分享链接发送给微信好友,也可以将穿搭公开到社区。分享链接的有效期以创建时你选择的有效期为准,到期、被你取消或因违规被封禁后自动失效。', + ), + li( + '你公开到社区的内容需经我们审核后展示。我们有权对不符合法律法规或本协议要求的内容不予展示、予以删除,并视情节对账号采取相应措施。', + ), + h('5. 额度与会员'), + li( + '部分功能需要消耗额度(如衣物上传、AI 推荐、分享),额度可通过购买、活动赠与等方式获得。不同额度类型的有效期、每日可用次数等规则以购买页面、额度明细或相关活动说明为准。', + ), + li( + 'AI 能力依赖第三方模型与网络环境,可能因不可控因素出现处理延迟、处理失败等情况,我们会尽力修复并完善体验,但不对识别与生成结果的准确性、及时性作出保证。', + ), + ], + }, + { + title: '付费服务与退款', + blocks: [ + p( + '本服务中的会员及各类额度包均为虚拟商品,通过微信小程序虚拟支付等微信认可的支付方式完成购买;不同终端可能适用不同的支付通道与规则。商品价格、包含权益与有效期以购买页面展示为准。', + ), + p( + '虚拟商品一经消耗(包括使用额度、享受会员权益)不支持退款;未消耗的部分,你可以在订单页面提交退款申请,我们将在收到申请后的【3 个工作日】内完成审核,审核通过后按原支付路径退回。', + ), + li( + '通过 iOS 终端、依据 Apple 相关规则购买并支付的订单,其退款请按照 Apple 的退款规则通过 App Store 申请,我们将在收到 Apple 处理结果后同步订单状态。', + ), + li( + '因我们自身原因导致购买失败、服务无法提供的,我们将为你原路退款或补发相应权益。', + ), + li( + '你理解并同意,虚拟商品属于在线交付的数字化商品,依据《中华人民共和国消费者权益保护法》第二十五条等规定,已开始交付或已消费的虚拟商品不适用七天无理由退货。', + ), + ], + }, + { + title: '用户行为规范', + blocks: [ + p( + '你承诺对你通过本服务上传、发布的一切内容负责,并保证你上传的衣物照片为你本人拍摄或已取得合法授权,不得上传他人肖像、他人未公开的私人衣物信息或任何来源不明的图片。', + ), + p('你不得利用本服务上传、发布、传播含有下列内容的信息:'), + li('违反宪法或法律法规规定、危害国家安全、泄露国家秘密、破坏国家统一的;'), + li('煽动民族仇恨、民族歧视,破坏民族团结,或宣扬邪教、封建迷信的;'), + li('淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;'), + li('侮辱、诽谤他人,或侵害他人隐私、名誉、肖像、知识产权及其他合法权益的;'), + li('散布广告、垃圾信息、虚假信息,或含有病毒、木马等恶意程序代码的;'), + li('法律法规禁止的其他内容。'), + p('你不得利用本服务从事下列行为:'), + li('使用外挂、插件、脚本等非官方工具,或以批量注册、刷量、作弊等方式获取不正当利益;'), + li('爬取、复制、镜像本服务的任何数据或内容,或对本服务进行反向工程、反编译、破解;'), + li('以任何方式干扰、破坏本服务的正常运行,或危害本服务及关联系统的安全。'), + p( + '如你违反本协议,我们可视情节采取警示提醒、删除违规内容、限制功能、暂停或终止服务等措施,并保留追究法律责任的权利。因你的行为给他人或我们造成损失的,你应独立承担全部责任。', + ), + p( + '如你认为本服务中的内容侵犯了你的合法权益,可通过本协议第十五条的方式向我们投诉,我们将在收到有效通知后及时核实处理。', + ), + ], + }, + { + title: '知识产权', + blocks: [ + p( + '本服务及其相关的软件、界面设计、图标、文字、图形、商标、标识等的知识产权归我们或相应权利人所有,未经书面许可,你不得复制、修改、传播或用于任何商业用途。', + ), + p( + '你上传的照片等用户内容,其权利归你或原权利人所有。为向你提供与优化本服务,你授予我们在全球范围内免费的、非独家的使用许可,允许我们在提供本服务所必需的范围内使用上述内容(例如存储、抠图处理、生成搭配图、按你的选择向你或你指定的人展示)。该许可在你删除相应内容或注销账号后终止,法律法规要求留存的除外。', + ), + p( + '你理解并同意,本服务生成的衣物处理图、穿搭组合等可用于本服务内的展示与功能实现。你不得将本服务提供的内容或数据用于商业用途,法律另有规定或我们书面许可的除外。', + ), + ], + }, + { + title: '个人信息保护', + blocks: [ + p( + '我们重视个人信息保护,将按照法律法规的要求处理你的个人信息。我们收集、使用、存储、共享和保护你个人信息的具体规则,详见《隐私政策》。', + ), + p( + '《隐私政策》是本协议不可分割的组成部分,与本协议具有同等效力。你在使用本服务前,请一并阅读并理解该政策。', + ), + ], + }, + { + title: '服务的变更、中断与终止', + blocks: [ + p( + '我们有权根据业务发展需要变更、暂停或终止部分或全部服务。涉及你重大权益的变更,我们将通过页面公告、弹窗或其他合理方式提前通知你。', + ), + p('出现下列情形之一的,我们可能中断或终止向你提供服务:'), + li('你违反本协议或相关法律法规;'), + li('你的账号被有权机关依法处置,或应法律法规、监管要求配合处理;'), + li('发生不可抗力、重大网络故障、第三方服务中断、系统升级维护等情形;'), + li('其他为我们合理判断为必要的情形。'), + p( + '服务终止后,我们将依法处理你的个人信息与相关数据。你账号内的额度、会员权益与内容将无法继续使用或恢复,请你及时自行留存需要的资料。', + ), + ], + }, + { + title: '免责声明与责任限制', + blocks: [ + p( + '本服务按"现状"和"可得到"的状态提供。对于衣物识别结果、AI 穿搭建议、星座运势内容的准确性、完整性、及时性与适用性,我们不作任何明示或默示的保证。你应自行判断并承担使用相关结果的风险。', + ), + p( + '因不可抗力、网络故障、设备故障、第三方服务(如微信、支付通道)中断、黑客攻击、系统维护或你自身操作不当等导致服务中断、数据延迟或丢失的,我们不承担赔偿责任,但将尽合理努力减少影响。', + ), + p( + '在法律允许的最大范围内,我们就本服务向你所承担的赔偿责任总额,不超过你在【过去 12 个月】内为本服务实际支付的费用。', + ), + p( + '请你及时自行保存重要内容。对于超出法律规定范围的删除、未备份导致的数据丢失,我们不承担责任。', + ), + ], + }, + { + title: '未成年人使用', + blocks: [ + p( + '若你未满 18 周岁,应在监护人的陪同与指导下阅读本协议并使用本服务;若你未满 14 周岁,请你的监护人阅读并同意《隐私政策》后,再使用本服务。', + ), + p( + '监护人应对未成年人的使用行为进行指导与监督,并对其使用本服务(包括购买虚拟商品)的行为负责。如未成年人未经监护人同意进行消费,监护人可通过本协议第十五条的方式联系我们协商处理。', + ), + ], + }, + { + title: '协议的更新', + blocks: [ + p( + '我们可能根据法律法规变化及业务调整对本协议进行更新。更新后的协议将在本页面公示,并同步更新"更新日期"。', + ), + p( + '更新后的协议自公示之日起生效(另有说明的除外)。若你不同意更新内容,请停止使用本服务;你继续使用本服务的,视为你接受更新后的协议。', + ), + ], + }, + { + title: '法律适用与争议解决', + blocks: [ + p('本协议的订立、效力、履行、解释及争议解决,均适用中华人民共和国大陆地区法律。'), + p( + '因本协议或本服务产生的争议,双方应先友好协商解决;协商不成的,任何一方可向【开发者所在地】有管辖权的人民法院提起诉讼。', + ), + ], + }, + { + title: '联系我们', + blocks: [ + p('如你对本协议有任何疑问、意见或建议,可通过以下方式与我们联系:'), + li(`运营主体:${LEGAL_META.operatorName}`), + li(`联系邮箱:${LEGAL_META.contactEmail}`), + li(`联系电话:${LEGAL_META.contactPhone}`), + p('我们将在收到你的反馈后 15 个工作日内予以答复。'), + ], + }, +] + +const PRIVACY_SECTIONS: LegalSection[] = [ + { + title: '我们收集和使用的信息', + blocks: [ + p( + `本政策由 ${LEGAL_META.operatorName}(以下简称"我们")发布,适用于"${LEGAL_META.appName}"微信小程序。我们遵循合法、正当、必要和诚信原则,仅处理实现小程序功能所必需的信息。我们收集的信息及用途如下:`, + ), + h('1. 微信授权登录信息'), + li( + '信息内容:你使用微信登录时,我们通过微信获取登录凭证,并据此生成你的账号标识(如 openid)及登录状态凭证。', + ), + li( + '使用目的:创建并识别你的账号、维持登录状态、将你的衣橱数据、额度与订单与账号对应。', + ), + li('不提供的影响:你将无法登录并使用本服务。'), + h('2. 你授权提供的微信昵称、头像'), + li( + '信息内容:你在登录时或通过"我的—编辑资料"主动授权选择的微信头像、填写的昵称。', + ), + li( + '使用目的:展示你的个人资料,以及在你分享衣橱、公开穿搭到社区时标识内容发布者。', + ), + li('不提供的影响:不影响衣橱、识别、推荐等核心功能,但资料区与社区署名将使用默认信息。'), + h('3. 你选中或拍摄的衣物照片'), + li( + '信息内容:你通过"拍照"或"从相册选择"提交的衣物照片,及经服务处理后生成的抠图图片与识别结果。', + ), + li( + '使用目的:完成图片上传、AI 抠图与衣物属性识别,在你的账号下生成并管理电子衣橱;在你主动选择分享时,向被分享者展示相应图片。', + ), + li('不提供的影响:你将无法使用衣物上传与识别功能。'), + h('4. 摄像头权限'), + li('信息内容:仅在你选择"拍照"方式上传衣物照片时调用摄像头。'), + li('使用目的:拍摄衣物照片用于上传与识别。'), + li('不提供的影响:你可以改用"从相册选择",其他功能不受影响。'), + h('5. 性别与生日'), + li('信息内容:你在"编辑资料"中自愿填写的性别、生日。'), + li('使用目的:展示星座运势内容,以及用于穿搭推荐的性别、季节适配等个性化处理。'), + li('不提供的影响:不影响其他功能,仅运势与部分个性化内容无法展示。'), + h('6. 订单与交易信息'), + li( + '信息内容:你购买会员或额度包时产生的订单号、商品名称、金额、支付渠道、订单状态、支付与退款时间。', + ), + li( + '使用目的:完成下单与权益发放、订单查询、退款审核与售后处理、对账与争议解决。', + ), + li( + '说明:支付由微信支付、小程序虚拟支付通道完成,我们不会收集你的银行卡号、支付密码等支付敏感信息。', + ), + h('7. 服务使用记录'), + li( + '信息内容:你的衣物数量与状态、额度获取与消耗流水、收藏与点赞记录、分享记录、通知与已读状态。', + ), + li('使用目的:向你展示额度明细与通知消息,同步内容状态,改进与优化服务体验。'), + h('8. 设备与日志信息'), + li( + '信息内容:设备型号、操作系统与版本、微信版本、网络类型、页面操作日志、接口错误日志与崩溃日志。', + ), + li('使用目的:定位与修复故障、进行安全风控与异常排查、保障服务稳定运行。'), + h('9. 你主动提交的反馈信息'), + li( + '信息内容:你通过客服与反馈渠道提交的文字说明、联系方式及你主动上传的截图。', + ), + li('使用目的:核实并处理你的问题、意见与投诉,必要时向你反馈处理结果。'), + ], + }, + { + title: '我们不会收集的信息', + blocks: [ + p('我们不会收集与实现本小程序功能无关的个人信息,包括:'), + li('你的地理位置信息(本小程序未申请位置权限);'), + li('你的通讯录、通话记录、短信记录;'), + li('你相册中的全部照片(我们仅处理你在选择图片时明确选中的那一张);'), + li('你的生物识别信息(如人脸、指纹)等其他敏感个人信息。'), + p( + '若未来新增功能确需处理新的个人信息,我们将通过页面告知、弹窗确认等方式向你说明并依法取得你的同意。', + ), + ], + }, + { + title: '我们如何保护你的信息', + blocks: [ + p('安全措施:'), + li('我们通过 HTTPS 加密传输你的信息,防止信息在传输过程中被窃取或篡改;'), + li('对登录凭证等敏感数据采取加密或脱敏存储,并实施严格的访问权限控制;'), + li('对内部人员进行数据安全培训与保密约束,记录并审计数据访问行为。'), + p('存储地点:你的个人信息存储于中华人民共和国境内的服务器。'), + p( + '存储期限:在你使用本服务期间持续保存。你删除相应内容或注销账号后,我们将删除或匿名化处理你的个人信息;法律法规另有规定的(例如《中华人民共和国电子商务法》要求商品和服务信息、交易信息保存时间自交易完成之日起不少于三年),我们将按法定期限保存。', + ), + ], + }, + { + title: '我们如何共享、转让、公开披露信息', + blocks: [ + p('我们不会出售你的个人信息。为实现本小程序功能所必需,我们可能与下列第三方共享必要信息:'), + li( + '微信 / 腾讯:用于微信授权登录、小程序运行环境与支付(含小程序虚拟支付、订单核销),相关处理适用微信平台的隐私规则;', + ), + li( + '云服务与对象存储服务商:用于服务器托管、数据存储与图片存储,其仅作为受托处理方按我们的要求处理信息;', + ), + li( + 'AI 能力服务商:如我们使用第三方 AI 服务完成衣物图片的抠图与识别,其仅在处理你上传的衣物图片所必需的范围内接触信息,不得用于其他目的。', + ), + p( + '我们将通过协议约束上述第三方的保密与数据安全义务,并要求其仅按本政策及我们的要求处理信息。', + ), + p( + '转让:因合并、分立、收购、资产转让等原因需要转移个人信息的,我们会告知你接收方的名称与联系方式,并要求其继续受本政策约束;否则我们将重新征求你的同意。', + ), + p('公开披露:除下列情形外,我们不会公开披露你的个人信息:'), + li('取得你的明确同意;'), + li('依据法律法规规定或有权机关(司法机关、行政机关等)的合法要求;'), + li('为保护公众、他人或我们的人身财产安全所必需的情形。'), + p( + '请特别注意:当你主动创建衣橱分享链接或公开穿搭到社区时,你的昵称、头像及你选择分享的衣物图片将对被分享者或社区其他用户可见。请你谨慎选择分享范围,并避免在图片中包含门牌、单据等包含个人信息的元素。', + ), + ], + }, + { + title: '本地存储与设备识别', + blocks: [ + p( + '为实现登录态保持与体验优化的目的,本小程序会在你的设备本地缓存必要信息,包括登录凭证、账号基础资料、未读消息数、AI 任务与待确认衣物记录等。这些信息仅用于本小程序的运行,不会用于其他目的。', + ), + p( + '你可以通过微信客户端清除小程序缓存或卸载小程序来删除本地缓存数据;清除后你需要重新登录,未同步的本地任务记录也将丢失。', + ), + ], + }, + { + title: '你的权利', + blocks: [ + h('1. 查阅、复制与更正'), + li( + '你可以通过"我的—编辑资料"查看并修改昵称、头像、性别、生日;通过"我的—额度明细 / 我的订单"查看额度和订单信息。如需其他个人信息副本,可通过本政策第十条的方式联系我们。', + ), + h('2. 删除'), + li( + '你可以删除衣物、穿搭等内容。删除后我们将停止对相应内容的处理与展示;法律法规要求留存的除外。', + ), + h('3. 撤回同意'), + li( + '你可以在"微信—设置—隐私—授权管理"或手机系统设置中关闭相机、相册等权限授权。撤回后我们将不再收集相应信息,但不影响撤回前基于你的同意已进行的处理。你也可以通过注销账号的方式撤回全部授权。', + ), + h('4. 注销账号'), + li( + '你可以通过本政策第十条的方式申请注销账号。注销完成后我们将删除或匿名化处理你的个人信息,但法律规定需保留的信息除外。请注意,注销后账号内的衣橱数据、穿搭、额度与会员权益均不可恢复。', + ), + h('5. 投诉与举报'), + li( + '如你认为我们的个人信息处理行为损害了你的合法权益,可通过本政策第十条的方式向我们投诉,我们将在 15 个工作日内答复。你也可以向网信、市场监管等主管部门投诉、举报。', + ), + ], + }, + { + title: '未成年人保护', + blocks: [ + p( + '本小程序主要面向成年人。若你未满 18 周岁,请在监护人的陪同与指导下使用本服务;若你未满 14 周岁,请你的监护人阅读并同意本政策后,再使用本服务。', + ), + p( + '我们不会主动收集未成年人的个人信息,也不会利用未成年人的个人信息进行个性化推送。若监护人发现未成年人在未取得同意的情况下向我们提供了个人信息,可通过本政策第十条的方式联系我们删除。', + ), + ], + }, + { + title: '本政策的更新', + blocks: [ + p( + '我们可能根据法律法规变化、业务功能调整而更新本政策。更新后我们将在本页面公示,并同步更新"更新日期"。', + ), + p( + '涉及信息处理目的、方式、信息种类等重大变更的,我们将通过弹窗、公告等显著方式提示你,并在依法需要时重新取得你的同意。', + ), + ], + }, + { + title: '本政策的适用', + blocks: [ + p( + '本政策是《用户协议》不可分割的组成部分。两者就个人信息处理事项的约定不一致的,以本政策为准。', + ), + p( + '本政策不适用于由第三方(如你通过分享链接跳转的其他服务)独立向你提供的服务,你应另行阅读其隐私政策。', + ), + ], + }, + { + title: '如何联系我们', + blocks: [ + p('如你对本政策或你的个人信息有任何疑问、意见或投诉,可通过以下方式联系我们:'), + li(`运营主体:${LEGAL_META.operatorName}`), + li(`联系邮箱:${LEGAL_META.contactEmail}`), + li(`联系电话:${LEGAL_META.contactPhone}`), + li(`个人信息保护负责人邮箱:${LEGAL_META.officerEmail}`), + p('我们将在收到你的反馈后 15 个工作日内予以答复。'), + ], + }, +] + +export const LEGAL_DOCS: Record<'user' | 'privacy', LegalDoc> = { + user: { + key: 'user', + title: `${LEGAL_META.appName}用户服务协议`, + enTitle: 'User Agreement', + effectiveAt: LEGAL_META.effectiveAt, + updatedAt: LEGAL_META.updatedAt, + intro: [ + `欢迎使用"${LEGAL_META.appName}"微信小程序!本协议是你与我们之间关于使用本服务所订立的协议,请你仔细阅读并充分理解各条款内容。`, + '特别提示:本协议涉及账号规则、付费与退款、用户行为规范、免责声明与责任限制等内容,其中以【】标注的条款请你重点关注。', + '你使用本服务即表示你已阅读并同意本协议;如你不同意,请停止使用本服务。', + ], + sections: USER_SECTIONS, + }, + privacy: { + key: 'privacy', + title: `${LEGAL_META.appName}隐私政策`, + enTitle: 'Privacy Policy', + effectiveAt: LEGAL_META.effectiveAt, + updatedAt: LEGAL_META.updatedAt, + intro: [ + '我们深知个人信息对你的重要性,并会尽力保护你的个人信息安全可靠。本政策将帮助你了解我们如何收集、使用、存储、共享和保护你的个人信息,以及你享有的权利与行使方式。', + '在填写、上传图片等信息前,请你先阅读并理解本政策。其中与你的个人信息权益存在重大关系的条款,我们已通过【】或加粗方式提示你注意。', + '你开始使用本服务,即表示你已阅读并同意本政策;如你不同意,请停止使用本服务。', + ], + sections: PRIVACY_SECTIONS, + }, +}