diff --git a/.workbuddy/memory/2026-07-15.md b/.workbuddy/memory/2026-07-15.md index 9efa2bc..02edfa9 100644 --- a/.workbuddy/memory/2026-07-15.md +++ b/.workbuddy/memory/2026-07-15.md @@ -199,3 +199,47 @@ - 历史数据清理:提供 `sql/shop_cart_merge_duplicates.sql`,按 (user_id,goods_id,sku_id) 分组合并 cart_num 并删除重复行(sku_id 可能为 NULL,用 `<=>` null-safe 匹配);执行前先备份 - 编译验证:Maven compile 成功 - 后端需重启服务才生效 + +## 购物车加购报 spec_info 字段缺失 +- 现象:添加购物车时报 `Unknown column 'spec_info' in 'field list'` +- 原因:后端实体 `ShopCart.java` 定义了 `specInfo` 字段(存储"颜色:红色|尺寸:L"这类规格信息),但数据库表 `shop_cart` 未建 `spec_info` 列;`ShopCartParam`、`OrderBusinessService` 均在用该字段,属设计需保留 +- 修复:新建迁移脚本 `/Users/gxwebsoft/JAVA/guilixu-java/sql/shop_cart_add_spec_info.sql`,`ALTER TABLE shop_cart ADD COLUMN spec_info VARCHAR(500) NULL ... AFTER spec`,需在数据库执行 + +## 修复登录页「未授权手机号」无降级入口 +- 现象:用户反复反馈"又又又登录不了了",截图显示点击「手机号快捷登录」按钮后 toast 弹「未授权手机号」,用户被困在登录页 +- 根因:`src/passport/login.tsx` 第 162-173 行 `onGetPhoneNumber` 回调里 `detail.code` 为 undefined(用户拒绝授权,errMsg 含 `user deny`/`fail`)时只 toast 了一句生硬文案,没有任何引导。但项目里其实有 `/passport/sms-login` 短信登录页(且在 `app.config.ts` 路由表里),`login.tsx` 却完全没暴露这个入口。`register.tsx` 同样有此问题,且其 weapp 环境下隐藏了短信按钮(原 `{!isWeapp && ...}` 判断) +- 修复(2 个文件 + 1 个 scss): + 1. `src/passport/login.tsx` + - 新增 `goSmsLogin()` 函数:透传 `redirect` + 邀请参数(`inviter`/`source`/`t`)跳 `/passport/sms-login` + - 新增 `showPhoneAuthFailedModal(errMsg)`:按 errMsg 区分场景弹窗引导 + - `user deny` → "您已取消微信手机号授权,可改用短信验证码登录" + - `privacy` → "微信未授权获取手机号,可改用短信验证码登录" + - `no permission` → "小程序暂未获得手机号授权,可改用短信验证码登录" + - 其他 → 通用"请尝试使用短信验证码登录" + - 弹窗带"短信登录"按钮自动跳 sms-login,"我知道了"关闭 + - 失败分支从 `Taro.showToast` 改为 `showPhoneAuthFailedModal(errMsg)` + - 主登录按钮下方新增长期可见的「使用短信验证码登录」链接(仿 register.tsx 的 goSmsLogin) + 2. `src/passport/login.scss` + - 新增 `.login-methods__alt` / `.login-methods__alt-link` 样式(白色下划线小字,居中) + 3. `src/passport/register.tsx` + - 同步加 `showPhoneAuthFailedModal(errMsg)` 弹窗引导 + - 去掉 `{!isWeapp && }` 包裹,让 weapp 下也始终显示短信按钮作为降级入口 +- 类型检查:`npx tsc --noEmit` 无报错 +- 业务规则:项目当前仅信任微信一键登录作为主路径,短信登录一直是兜底通道。后续如发现用户量大,可考虑直接在登录页主流程就两个按钮并排(不再让微信登录独占) + +## sms-login 页面 UI 重构 +- 原状:截图显示基本是裸的——只看到两个孤零零的 placeholder 文字,发送验证码按钮和登录按钮都看不见。NutUI 的 `Input` + `Button` 在 Taro 小程序上经常渲染不出边框/背景,外层 `flex justify-between` 也没把验证码按钮挤出来 +- 改造(2 个文件): + 1. `src/passport/sms-login.tsx` — 弃用 NutUI 改用 Taro 原生 `View/Text/Input`,重做整页布局 + - 渐变背景 + 浮动光圈(和 login.tsx 同一套色系 `#667eea → #764ba2`) + - 顶部圆形 logo(140rpx 白底圆角 + 阴影)+ 标题 + 副标题 + - 白色卡片输入区(圆角 28rpx + 阴影):手机号(📞)+ 灰线分隔 + 验证码(🔐 + 绿色胶囊「获取验证码」按钮) + - 绿色渐变登录大按钮(圆角 50rpx + 阴影 + 字间距 4rpx) + - 「← 返回微信快捷登录」白色下划线链接(一键 navigateBack 回到 login.tsx,避免用户被困) + - 底部协议 + 服务协议/隐私政策超链接 + - 业务逻辑(state、60s 倒计时、handleSendCode/handleLogin 校验、redirect 跳转、邀请参数透传)全部保留 + 2. `src/passport/sms-login.scss`(新文件)— 配套样式:渐变背景、浮动光圈动画(gradientMove + float)、卡片毛玻璃、按钮 hover 效果(scale 0.98)、分阶段入场动画(slideDown / slideUp / fadeIn) +- 类型检查:`npx tsc --noEmit` 无报错(项目原本的 `@tarojs/taro` 类型错误与本次无关) +- 编译验证:`npx taro build --type weapp` 14.89s 成功 +- 设计稿预览:`.workbuddy/sms-login-preview.html`(按 375×812 真机尺寸渲染的 mockup,可直接浏览器打开看) +- 业务规则:login/register/sms-login 三个页面已统一视觉风格(同一套渐变 + 圆形 logo + 大字标题 + 白色卡片),后续新增 passport 类页面照搬这一套即可 diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md index 52fec7e..451e5cf 100644 --- a/.workbuddy/memory/MEMORY.md +++ b/.workbuddy/memory/MEMORY.md @@ -56,6 +56,15 @@ - ⚠️ **`requiredPrivateInfos` 字段只接受位置类 API 白名单**(chooseAddress/chooseLocation/choosePoi/getFuzzyLocation/getLocation/onLocationChange/startLocationUpdate/startLocationUpdateBackground),不要加 `chooseImage`/`chooseMedia` 等非位置类,会导致 app.json 解析失败 - 直接调 `Taro.chooseImage` 的页面:`pages/order/evaluate/index.tsx`、`pages/after-sale/apply/index.tsx`、`pages/store/orders/index.tsx` —— 这些页面绕过了 `uploadFile`,需要单独加 `ensurePrivacyAuthorized` 预检 +## 手机号授权登录降级方案(2026-07-15) +- **业务现实**:`getPhoneNumber` 按钮被用户拒绝后,微信会短期"记住"用户的选择,再次点击会直接 fail(`errMsg: getPhoneNumber:fail user deny`)。一旦进入这个状态,前端必须提供降级入口 +- **统一模式**:`passport/login.tsx` 和 `passport/register.tsx` 都有 `getPhoneNumber` 回调,**不允许**只 toast 一句"未授权手机号"就完事 + - 必做:失败时调 `showPhoneAuthFailedModal(errMsg)`,按 errMsg 区分场景弹窗(user deny / privacy / no permission / 通用) + - 弹窗带"短信登录"按钮自动跳 `/passport/sms-login`(透传 redirect + 邀请参数 inviter/source/t) + - 必做:主登录按钮**长期可见**地提供"使用短信验证码登录"链接,不要依赖弹窗 +- **短信登录页**:`/passport/sms-login`(路由表里已有),调 `loginBySms` + `sendSmsCaptcha` 接口 +- **已落地页面**:`passport/login.tsx`(新增 goSmsLogin + showPhoneAuthFailedModal + alt 链接)、`passport/register.tsx`(新增 showPhoneAuthFailedModal + weapp 下也显示短信按钮) + ## 分享 / 朋友圈能力(2026-07-13 接入) - 统一封装:`src/hooks/useShare.ts`(`useShare({title, path?, query?, imageUrl?, enableTimeline?, enableCopyUrl?})`,一次注册 `useShareAppMessage` + `useShareTimeline` + 可选 `onCopyUrl` 复制链接;自动在 path/query 追加 `inviter=${当前用户id}` 做裂变归因;`isTimelineSinglePage()` 判断朋友圈单页模式 scene===1154) - 海报组件:`src/components/SharePoster/index.tsx`(Canvas 2D 绘制封面+标题+价格+小程序码,返回临时图路径作 imageUrl) diff --git a/.workbuddy/sms-login-preview.html b/.workbuddy/sms-login-preview.html new file mode 100644 index 0000000..9a636db --- /dev/null +++ b/.workbuddy/sms-login-preview.html @@ -0,0 +1,309 @@ + + + + +sms-login UI 预览 + + + +

sms-login.tsx 新 UI 预览(按真机 375×812 渲染)

+ +
+
+ 18:36 + 📶 🔋 +
+ + +
+
+
+
+ +
+
+ +
验证码登录
+
未注册的手机号验证后将自动注册
+
+ +
+
+ 📞 + +
+
+
+ 🔐 + +
获取验证码
+
+
+ +
登录 / 注册
+ +
+ + 返回微信快捷登录 +
+
+ +
+ 登录即代表您已阅读并同意 + 《服务协议》 + + 《隐私政策》 +
+
+
+ +
+ 视觉变更:
+ ① 紫蓝渐变背景 + 浮动光圈(和 login.tsx 同一套色系)
+ ② 顶部圆形 logo + 大字标题 + 副标题
+ ③ 白色卡片输入区(圆角 28px + 阴影),手机号/验证码图标 + 灰线分隔
+ ④ 验证码右侧绿色胶囊「获取验证码」按钮(倒计时会变成「60s 后重试」)
+ ⑤ 绿色渐变登录大按钮(圆角 50px + 阴影)
+ ⑥ 「← 返回微信快捷登录」白色下划线链接(避免用户被困住)
+ ⑦ 底部协议 + 服务协议/隐私政策超链接
+
+ 业务逻辑保留:表单 state、60s 倒计时、handleSendCode/handleLogin 校验、redirect 跳转、邀请参数透传(inviter/source/t)全部不变 +
+ + diff --git a/src/passport/login.scss b/src/passport/login.scss index d4d807d..7e886a8 100644 --- a/src/passport/login.scss +++ b/src/passport/login.scss @@ -206,6 +206,21 @@ margin-top: 10px; } +// 降级登录入口(短信验证码) +.login-methods__alt { + text-align: center; + margin-top: 4px; +} + +.login-methods__alt-link { + display: inline-block; + font-size: 26px; + color: rgba(255, 255, 255, 0.85); + text-decoration: underline; + padding: 8px 16px; + letter-spacing: 0.5px; +} + // 登录按钮 .login-btn { height: 96px; diff --git a/src/passport/login.tsx b/src/passport/login.tsx index dd0b217..d2952a4 100644 --- a/src/passport/login.tsx +++ b/src/passport/login.tsx @@ -158,6 +158,44 @@ const Login = () => { await Taro.redirectTo({ url: redirectUrl }) } + /** 跳转到短信验证码登录(降级方案) */ + const goSmsLogin = () => { + const params: Record = {} + if (redirectUrl) params.redirect = redirectUrl + // 透传邀请参数,避免从登录页切到短信登录时丢失 inviter + const inviteParams = parseInviteParams({ query: router?.params }) + if (inviteParams?.inviter) params.inviter = inviteParams.inviter + if (inviteParams?.source) params.source = inviteParams.source + if (inviteParams?.t) params.t = inviteParams.t + + const qs = Object.entries(params) + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`) + .join('&') + Taro.navigateTo({ url: `/passport/sms-login${qs ? `?${qs}` : ''}` }) + } + + /** 手机号授权失败时弹窗引导走短信登录(区分原因) */ + const showPhoneAuthFailedModal = (errMsg?: string) => { + let content = '请尝试使用短信验证码登录' + if (errMsg?.includes('user deny')) { + content = '您已取消微信手机号授权,可改用短信验证码登录' + } else if (errMsg?.includes('privacy')) { + content = '微信未授权获取手机号,可改用短信验证码登录' + } else if (errMsg?.includes('no permission')) { + content = '小程序暂未获得手机号授权,可改用短信验证码登录' + } + Taro.showModal({ + title: '未获取到手机号', + content, + confirmText: '短信登录', + cancelText: '我知道了', + confirmColor: '#07c160', + success: (res) => { + if (res.confirm) goSmsLogin() + }, + }) + } + /** 手机号快捷登录 */ const handleGetPhoneNumber = async ({ detail }: GetPhoneNumberEvent) => { if (!isAgree) { @@ -168,7 +206,7 @@ const Login = () => { const { code: phoneCode, errMsg } = detail || {} if (!phoneCode || (errMsg && errMsg.includes('fail'))) { - Taro.showToast({ title: '未授权手机号', icon: 'none' }) + showPhoneAuthFailedModal(errMsg) return } @@ -282,8 +320,17 @@ const Login = () => { 手机号快捷登录 - - 点击上方按钮,快速登录 + {/**/} + {/* 点击上方按钮,快速登录*/} + {/**/} + + + goSmsLogin()} + > + 使用短信验证码登录 + diff --git a/src/passport/register.tsx b/src/passport/register.tsx index 2c660d0..aab1fa7 100644 --- a/src/passport/register.tsx +++ b/src/passport/register.tsx @@ -150,6 +150,28 @@ const Register = () => { await Taro.redirectTo({ url: redirectUrl }) } + /** 手机号授权失败时弹窗引导走短信注册/登录(区分原因) */ + const showPhoneAuthFailedModal = (errMsg?: string) => { + let content = '请尝试使用短信验证码注册/登录' + if (errMsg?.includes('user deny')) { + content = '您已取消微信手机号授权,可改用短信验证码注册/登录' + } else if (errMsg?.includes('privacy')) { + content = '微信未授权获取手机号,可改用短信验证码注册/登录' + } else if (errMsg?.includes('no permission')) { + content = '小程序暂未获得手机号授权,可改用短信验证码注册/登录' + } + Taro.showModal({ + title: '未获取到手机号', + content, + confirmText: '短信注册/登录', + cancelText: '我知道了', + confirmColor: '#07c160', + success: (res) => { + if (res.confirm) goSmsLogin() + }, + }) + } + const handleGetPhoneNumber = async ({ detail }: GetPhoneNumberEvent) => { if (!isAgree) { Taro.showToast({ title: '请先勾选同意协议', icon: 'none' }) @@ -159,7 +181,7 @@ const Register = () => { const { code: phoneCode, encryptedData, iv, errMsg } = detail || {} if (!phoneCode || (errMsg && errMsg.includes('fail'))) { - Taro.showToast({ title: '未授权手机号', icon: 'none' }) + showPhoneAuthFailedModal(errMsg) return } @@ -281,11 +303,9 @@ const Register = () => { 手机号一键注册/登录 - {!isWeapp && ( - - )} + diff --git a/src/passport/sms-login.scss b/src/passport/sms-login.scss new file mode 100644 index 0000000..0ccf226 --- /dev/null +++ b/src/passport/sms-login.scss @@ -0,0 +1,332 @@ +// 页面容器 +.page-sms-login { + min-height: 100vh; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + flex-direction: column; + position: relative; + overflow: hidden; +} + +// 背景装饰 +.sms-login-bg { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + pointer-events: none; + overflow: hidden; +} + +.sms-login-bg__gradient { + position: absolute; + top: -50%; + left: -50%; + right: -50%; + bottom: -50%; + background: radial-gradient(circle at 30% 50%, rgba(255, 255, 255, 0.1) 0%, transparent 50%), + radial-gradient(circle at 70% 80%, rgba(255, 255, 255, 0.08) 0%, transparent 50%); + animation: gradientMove 15s ease-in-out infinite; +} + +@keyframes gradientMove { + 0%, 100% { + transform: translate(0, 0) rotate(0deg); + } + 33% { + transform: translate(30px, -30px) rotate(120deg); + } + 66% { + transform: translate(-20px, 20px) rotate(240deg); + } +} + +.sms-login-bg__circle { + position: absolute; + border-radius: 50%; + opacity: 0.1; + background: #ffffff; + animation: float 20s ease-in-out infinite; +} + +.sms-login-bg__circle--1 { + width: 400px; + height: 400px; + top: -150px; + right: -100px; + animation-delay: 0s; +} + +.sms-login-bg__circle--2 { + width: 300px; + height: 300px; + bottom: 10%; + left: -100px; + animation-delay: -7s; +} + +@keyframes float { + 0%, 100% { + transform: translate(0, 0) scale(1); + } + 33% { + transform: translate(30px, -30px) scale(1.1); + } + 66% { + transform: translate(-20px, 20px) scale(0.9); + } +} + +// 内容区域 +.sms-login-content { + display: flex; + flex-direction: column; + min-height: 100vh; + padding: 0 50px; + position: relative; + z-index: 1; +} + +// 头部 Logo 和标题 +.sms-login-header { + display: flex; + flex-direction: column; + align-items: center; + padding-top: 120px; + margin-bottom: 60px; + animation: slideDown 0.8s ease-out; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.sms-login-logo { + width: 140px; + height: 140px; + border-radius: 35px; + background: rgba(255, 255, 255, 0.95); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 36px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + animation: logoFloat 3s ease-in-out infinite; +} + +@keyframes logoFloat { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-8px); + } +} + +.sms-login-logo__icon { + font-size: 72px; + line-height: 1; +} + +.sms-login-title { + font-size: 52px; + font-weight: 700; + color: #ffffff; + margin-bottom: 18px; + text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + letter-spacing: 2px; +} + +.sms-login-subtitle { + font-size: 26px; + color: rgba(255, 255, 255, 0.85); + text-align: center; +} + +// 表单卡片 +.sms-login-card { + background: rgba(255, 255, 255, 0.98); + border-radius: 28px; + padding: 12px 32px; + margin-bottom: 48px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.18); + animation: slideUp 0.6s ease-out 0.2s both; +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.sms-login-field { + display: flex; + align-items: center; + height: 110px; + position: relative; +} + +.sms-login-field--code { + padding: 0; +} + +.sms-login-field__icon-wrap { + display: flex; + align-items: center; + flex: 1; + min-width: 0; + height: 100%; +} + +.sms-login-field__icon { + font-size: 36px; + margin-right: 20px; + line-height: 1; + flex-shrink: 0; +} + +.sms-login-field__input { + flex: 1; + height: 110px; + font-size: 30px; + color: #333333; + letter-spacing: 1px; + background: transparent; + border: none; + outline: none; + padding: 0; + min-width: 0; +} + +.sms-login-field__placeholder { + color: #b8b8b8; + font-size: 30px; +} + +.sms-login-divider { + height: 1px; + background: linear-gradient(to right, transparent, #e8e8e8 20%, #e8e8e8 80%, transparent); + margin: 0 -8px; +} + +// 发送验证码按钮 +.sms-login-send { + flex-shrink: 0; + height: 64px; + padding: 0 22px; + border-radius: 32px; + background: linear-gradient(135deg, #07c160, #06ad56); + color: #ffffff; + font-size: 26px; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + margin-left: 16px; + box-shadow: 0 6px 20px rgba(7, 193, 96, 0.35); + letter-spacing: 0.5px; + white-space: nowrap; +} + +.sms-login-send--disabled { + background: #e0e0e0; + color: #999999; + box-shadow: none; +} + +// 登录按钮 +.sms-login-submit { + height: 100px; + width: 100%; + border-radius: 50px; + background: linear-gradient(135deg, #07c160, #06ad56); + color: #ffffff; + font-size: 34px; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + letter-spacing: 4px; + box-shadow: 0 12px 36px rgba(7, 193, 96, 0.4); + margin-bottom: 36px; + animation: slideUp 0.6s ease-out 0.35s both; +} + +.sms-login-submit--disabled { + background: #b8e0c5; + color: rgba(255, 255, 255, 0.85); + box-shadow: 0 4px 12px rgba(7, 193, 96, 0.15); +} + +.sms-login-submit--hover { + transform: scale(0.98); + opacity: 0.92; +} + +// 返回链接 +.sms-login-back { + display: flex; + align-items: center; + justify-content: center; + height: 80px; + margin-bottom: 32px; + animation: fadeIn 0.8s ease-out 0.5s both; +} + +.sms-login-back__icon { + font-size: 30px; + color: rgba(255, 255, 255, 0.9); + margin-right: 10px; + line-height: 1; +} + +.sms-login-back__text { + font-size: 28px; + color: rgba(255, 255, 255, 0.9); + text-decoration: underline; + letter-spacing: 1px; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +// 底部协议 +.sms-login-tips { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + padding: 0 40px; + margin-top: auto; + padding-bottom: 60px; + animation: fadeIn 0.8s ease-out 0.6s both; +} + +.sms-login-tips__text { + font-size: 22px; + color: rgba(255, 255, 255, 0.7); + line-height: 1.6; +} + +.sms-login-tips__link { + font-size: 22px; + color: #ffffff; + text-decoration: underline; + margin: 0 4px; + line-height: 1.6; +} diff --git a/src/passport/sms-login.tsx b/src/passport/sms-login.tsx index 4299e99..147dfda 100644 --- a/src/passport/sms-login.tsx +++ b/src/passport/sms-login.tsx @@ -1,10 +1,10 @@ import {useEffect, useState} from "react"; import Taro from '@tarojs/taro' -import { View } from '@tarojs/components' -import {Input, Button} from '@nutui/nutui-react-taro' +import { View, Text, Input } from '@tarojs/components' import {loginBySms, sendSmsCaptcha} from "@/api/passport/login"; import {LoginParam} from "@/api/passport/login/model"; import {checkAndHandleInviteRelation, hasPendingInvite, parseInviteParams, saveInviteParams, trackInviteSource} from "@/utils/invite"; +import './sms-login.scss' const SmsLogin = () => { const [loading, setLoading] = useState(false) @@ -55,12 +55,8 @@ const SmsLogin = () => { await Taro.redirectTo({ url: redirectUrl }) } - const reload = () => { - // sms-login 页面不是 tabBar 页面,无需调用 hideTabBar - } - useEffect(() => { - reload() + // sms-login 页面不是 tabBar 页面,无需调用 hideTabBar }, []) // 如果从分享/二维码链接进入短信登录页,先暂存邀请信息 @@ -78,7 +74,7 @@ const SmsLogin = () => { // 倒计时效果 useEffect(() => { - let timer: NodeJS.Timeout + let timer: ReturnType | undefined if (countdown > 0) { timer = setTimeout(() => { setCountdown(countdown - 1) @@ -218,52 +214,114 @@ const SmsLogin = () => { } } + const phoneValid = validatePhone(formData.phone) + const codeValid = formData.code.length === 6 + const canSubmit = phoneValid && codeValid && !loading + const sendDisabled = !phoneValid || sendingCode || countdown > 0 + + const sendBtnText = countdown > 0 + ? `${countdown}s 后重试` + : sendingCode + ? '发送中' + : '获取验证码' + return ( - <> - - - setFormData({...formData, phone: value})} - style={{backgroundColor: '#ffffff', borderRadius: '8px'}} - /> + + {/* 渐变背景 */} + + + + + + + + {/* 头部 Logo 和标题 */} + + + 📱 + + 验证码登录 + 未注册的手机号验证后将自动注册 - - setFormData({...formData, code: value})} - style={{ backgroundColor: '#ffffff', borderRadius: '8px'}} - /> - + + {/* 表单卡片 */} + + {/* 手机号 */} + + 📞 + setFormData({ ...formData, phone: e.detail.value })} + /> + + + + + {/* 验证码 */} + + + 🔐 + setFormData({ ...formData, code: e.detail.value })} + /> + + + {sendBtnText} + + - - + 《服务协议》 + + + Taro.navigateTo({ url: '/passport/agreement?type=privacy' })} + > + 《隐私政策》 + - + ) } export default SmsLogin