From 40783ccabe29127c0a1060a300985817023a4d3f Mon Sep 17 00:00:00 2001 From: ozh Date: Mon, 21 Sep 2026 17:08:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E5=86=B7=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E6=80=81=E6=A0=A1=E9=AA=8C=20+=20=E8=AE=BE?= =?UTF-8?q?=E5=A4=87/=E6=88=BF=E9=97=B4=E5=88=97=E8=A1=A8=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E6=89=93=E7=A3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 getLoginInfo(/v0/app/users/getLoginInfo)冷启动校验 token 有效性, http 层将 /v0/ 前缀路由到 user 服务;登录页 useDidShow 恢复 userInfo 后落地,restoring 态防并发登录 - 详情页指标卡配色从数值移到标签(对齐 web 端 MetricCard), 心率图标加底、设备信息卡补"设备信息"标题 - 设备列表新增"我的设备 N"分区标题,功能入口图标加圆底 - 房间列表改整卡圆角+行内分隔,删除按钮改浅红胶囊 - global.d.ts 补 png/jpg 模块声明 --- src/api/auth.ts | 6 ++ src/http/client.ts | 4 +- src/pages/device/feature/add.module.scss | 9 ++- src/pages/device/feature/detail.module.scss | 61 +++++++++++++++++-- src/pages/device/feature/detail.tsx | 29 +++++---- src/pages/device/index.module.scss | 67 +++++++++++++++++---- src/pages/device/index.tsx | 16 ++++- src/pages/login/index.tsx | 22 +++++-- src/pages/scene/select.module.scss | 36 ++++++++--- src/types/global.d.ts | 10 +++ 10 files changed, 213 insertions(+), 47 deletions(-) diff --git a/src/api/auth.ts b/src/api/auth.ts index 5ba1e16..f18d1cc 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -48,3 +48,9 @@ export function wxMiniappLogin(params: WxMiniappLoginParams): Promise)联调实测后修正字段 +export function getLoginInfo(): Promise { + return request.get('/v0/app/users/getLoginInfo') +} diff --git a/src/http/client.ts b/src/http/client.ts index 50f0111..d759e61 100644 --- a/src/http/client.ts +++ b/src/http/client.ts @@ -72,8 +72,8 @@ export async function request( if (tokenAtStart) headers.token = tokenAtStart } - // 登录接口走 user 服务前缀,其余业务接口走 biz 前缀 - const base = url.startsWith('/thirdPlatform/') ? USER_BASE : BIZ_BASE + // 登录接口与 users 系列冷启动校验接口走 user 服务前缀,其余业务接口走 biz 前缀 + const base = url.startsWith('/thirdPlatform/') || url.startsWith('/v0/') ? USER_BASE : BIZ_BASE let res: Taro.request.SuccessCallbackResult> try { diff --git a/src/pages/device/feature/add.module.scss b/src/pages/device/feature/add.module.scss index fdbd83b..3bd41f7 100644 --- a/src/pages/device/feature/add.module.scss +++ b/src/pages/device/feature/add.module.scss @@ -8,7 +8,8 @@ margin: 24px; padding: 40px 32px; background: $color-bg-white; - border-radius: $radius-md; + border-radius: $radius-lg; + box-shadow: $shadow-card; } .sectionTitle { @@ -32,11 +33,17 @@ height: 96px; padding: 0 32px; background: $color-bg-gray; + border: 1px solid transparent; border-radius: $radius-md; font-size: 28px; color: $color-text; box-sizing: border-box; margin-bottom: 32px; + + &:focus { + border-color: $color-primary; + background: $color-bg-white; + } } // 安装房间选择:label + 横向 chip 组 diff --git a/src/pages/device/feature/detail.module.scss b/src/pages/device/feature/detail.module.scss index 5c126e8..0489e0f 100644 --- a/src/pages/device/feature/detail.module.scss +++ b/src/pages/device/feature/detail.module.scss @@ -13,11 +13,12 @@ .heartCard { margin: 24px; padding: 32px; - background: $color-bg-white; + background: linear-gradient(135deg, #fff 0%, #fdf7f6 100%); border-radius: $radius-lg; box-shadow: $shadow-card; display: flex; align-items: flex-start; + border: 1px solid rgba(245, 34, 45, 0.06); } .heartLeft { @@ -27,7 +28,22 @@ min-width: 200px; } -.heartIcon { font-size: 72px; color: $color-danger; line-height: 1; } +// 心形图标:浅红圆底衬托,弱化大面积实色 +.heartIconWrap { + width: 88px; + height: 88px; + border-radius: $radius-circle; + background: $color-danger-light-bg; + display: flex; + align-items: center; + justify-content: center; +} + +.heartIcon { + font-size: 48px; + color: $color-danger; + line-height: 1; +} .heartValue { margin-top: 16px; @@ -87,21 +103,45 @@ width: 48.5%; box-sizing: border-box; margin-bottom: 20px; - padding: 24px; + padding: 28px 24px; background: $color-bg-white; border-radius: $radius-md; box-shadow: $shadow-card; } -.metricLabel { font-size: 26px; color: $color-text-secondary; } +// 指标名左侧小色条:六卡各自的语义色,替代大数字整体染色 +.metricLabel { + display: flex; + align-items: center; + font-size: 26px; + color: $color-text-secondary; + + &::before { + content: ''; + width: 8px; + height: 24px; + border-radius: $radius-pill; + margin-right: 12px; + background: $color-text-placeholder; + } +} + +.metricLabelBlue::before { background: $color-info; } +.metricLabelOrange::before { background: $color-warning; } +.metricLabelGreen::before { background: $color-success; } +.metricLabelRed::before { background: $color-danger; } .metricRow { - margin-top: 12px; + margin-top: 14px; display: flex; align-items: baseline; } -.metricValue { font-size: 44px; font-weight: 700; } +.metricValue { + font-size: 44px; + font-weight: 700; + color: $color-text; +} .metricBlue { color: $color-primary-deep; } .metricOrange { color: #fa8c16; } @@ -114,6 +154,15 @@ color: $color-text-tertiary; } +// 设备信息卡分区标题 +.infoTitle { + display: block; + margin: 24px 24px 16px; + font-size: 30px; + font-weight: 600; + color: $color-text; +} + .infoCard { margin: 4px 24px 24px; } diff --git a/src/pages/device/feature/detail.tsx b/src/pages/device/feature/detail.tsx index 0c07a64..e55898e 100644 --- a/src/pages/device/feature/detail.tsx +++ b/src/pages/device/feature/detail.tsx @@ -127,7 +127,9 @@ export default function Detail() { - ❤ + + ❤ + {fmt(signs?.heartRate)} BPM @@ -146,45 +148,46 @@ export default function Detail() { - 呼吸率 + 呼吸率 - {fmt(signs?.respiratoryRate)} + {fmt(signs?.respiratoryRate)} /min - 体动 + 体动 - {fmt(signs?.bodyMovement)} + {fmt(signs?.bodyMovement)} - 运动状态 + 运动状态 - {MOVE_STATE_MAP[signs?.moveState ?? -1] ?? '--'} + {MOVE_STATE_MAP[signs?.moveState ?? -1] ?? '--'} - 在床状态 + 在床状态 - {ONBED_STATUS_MAP[signs?.onbedStatus ?? -1] ?? '--'} + {ONBED_STATUS_MAP[signs?.onbedStatus ?? -1] ?? '--'} - 人体状态 + 人体状态 - {BODY_STATUS_MAP[signs?.bodyStatus ?? -1] ?? '--'} + {BODY_STATUS_MAP[signs?.bodyStatus ?? -1] ?? '--'} - 人体位置 + 人体位置 - {fmt(signs?.bodyPosition)} + {fmt(signs?.bodyPosition)} 分米 + 设备信息 diff --git a/src/pages/device/index.module.scss b/src/pages/device/index.module.scss index 0999987..9c2e721 100644 --- a/src/pages/device/index.module.scss +++ b/src/pages/device/index.module.scss @@ -15,7 +15,7 @@ // 绿色衬底:占位高度与浮卡上浮量(-40px)匹配,卡片叠在它上面而不是压进导航栏 .heroSpace { height: 64px; - background: #2ccb98; + background: linear-gradient(180deg, #2ccb98 0%, #35d6a5 100%); margin-bottom: -40px; position: relative; z-index: 1; @@ -27,7 +27,7 @@ padding: 8px 32px 32px; background: $color-bg-white; border-radius: $radius-lg; - box-shadow: $shadow-card; + box-shadow: 0 8px 24px rgba(44, 203, 152, 0.12); position: relative; z-index: 2; } @@ -38,6 +38,10 @@ align-items: center; justify-content: space-between; border-bottom: 1px solid $color-border; + + &:last-of-type { + border-bottom: none; + } } .familyLabel { @@ -75,7 +79,7 @@ // 房间管理 / 添加设备 两张并排功能卡 .funcRow { display: flex; - padding: 24px; + padding: 24px 24px 0; gap: 24px; } @@ -86,20 +90,30 @@ border-radius: $radius-md; box-shadow: $shadow-card; display: flex; - flex-direction: column; align-items: center; justify-content: center; + gap: 20px; } .funcCardPressed { background: $color-bg-press; } +// 左侧圆角浅绿图标底:延续「左图标色块」卡片语言 +.funcIconWrap { + width: 88px; + height: 88px; + border-radius: $radius-md; + background: $color-primary-light-bg; + display: flex; + align-items: center; + justify-content: center; +} + .funcIcon { - font-size: 56px; + font-size: 48px; color: $color-primary; line-height: 1; - margin-bottom: 12px; } .funcText { @@ -113,7 +127,7 @@ display: flex; flex-wrap: nowrap; overflow-x: auto; - padding: 0 24px 24px; + padding: 24px 24px 8px; gap: 16px; } @@ -121,16 +135,40 @@ flex-shrink: 0; padding: 12px 32px; background: $color-bg-white; + border: 1px solid $color-border; border-radius: $radius-pill; - box-shadow: $shadow-card; + font-size: 26px; + color: $color-text-secondary; } .roomChipActive { background: $color-primary-grad; + border-color: transparent; } .roomChipTextActive { color: #ffffff; + font-weight: 600; +} + +// 「我的设备」分区标题:筛选条与网格之间的视觉锚点 +.sectionTitle { + display: flex; + align-items: center; + padding: 24px 24px 16px; + font-size: 30px; + font-weight: 600; + color: $color-text; + + .sectionCount { + margin-left: 12px; + padding: 2px 16px; + background: $color-primary-light-bg; + border-radius: $radius-pill; + font-size: 22px; + font-weight: 500; + color: $color-primary-deep; + } } // 设备网格 @@ -144,7 +182,7 @@ .card { width: 48.5%; margin-bottom: 24px; - padding: 36px 20px 24px; + padding: 36px 20px 28px; box-sizing: border-box; background: $color-bg-white; border-radius: $radius-lg; @@ -152,6 +190,11 @@ display: flex; flex-direction: column; align-items: center; + border: 1px solid transparent; + + &:active { + border-color: $color-primary-light-border; + } } // 上半区(图标/名称/状态)点击进详情,下方解绑按钮独立命中 @@ -166,13 +209,14 @@ width: 108px; height: 108px; padding: 14px; - border-radius: $radius-circle; + border-radius: 32px; background: $color-primary-grad; display: flex; align-items: center; justify-content: center; margin-bottom: 20px; box-sizing: border-box; + box-shadow: 0 6px 16px rgba(44, 203, 152, 0.25); } .iconOffline { background: $color-bg-disabled; } @@ -228,13 +272,14 @@ .sheetItem { padding: 24px 32px; background: $color-bg-gray; + border: 2px solid transparent; border-radius: $radius-md; margin-bottom: 16px; } .sheetItemActive { background: $color-primary-light-bg; - border: 2px solid $color-primary; + border-color: $color-primary; } .sheetHead { diff --git a/src/pages/device/index.tsx b/src/pages/device/index.tsx index 650c802..04c9ab7 100644 --- a/src/pages/device/index.tsx +++ b/src/pages/device/index.tsx @@ -182,11 +182,15 @@ export default function DeviceList() { {/* 房间管理 + 添加设备 两张并排 */} - ⌂ + + ⌂ + 房间管理 - + + + + + 添加设备 @@ -215,6 +219,14 @@ export default function DeviceList() { )} + {/* 「我的设备」分区标题:数量随筛选联动 */} + {!loading && !loadError && ( + + 我的设备 + {deviceList.length} + + )} + {/* 设备网格 */} {loading ? ( diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index ab05e2d..dd7ffd0 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -4,6 +4,7 @@ import { useRouter, useDidShow } from '@tarojs/taro' import { useAppDispatch, useAppSelector } from '@/store/hooks' import { setAuth } from '@/store/authSlice' import { performWxLogin, completeUserInfoLogin, getLoginSubState } from './wxLoginFlow' +import { getLoginInfo } from '@/api/auth' import { useToast } from '@/hooks/useToast' import { ROUTES } from '@/constants/routes' import { reLaunch } from '@/utils/navigation' @@ -20,6 +21,8 @@ export default function Login() { // 注册中间态:needUserInfo(待用户资料授权)→ null(初始/完成) const [needAuth, setNeedAuth] = useState(false) const [openId, setOpenId] = useState('') + // 冷启动校验进行中:阻止 useDidShow 与按钮登录并发 + const [restoring, setRestoring] = useState(false) // 登录后落地页:携带 redirect 参数时优先回跳(守卫踢登录/分享卡直达场景),否则进首页 const afterLoginUrl = () => { @@ -28,9 +31,20 @@ export default function Login() { return target || ROUTES.HOME } - // 已有 token 直接进首页(本期不做 checkLogin 校验,spec D5) + // 冷启动 token 有效性校验:有效则恢复 userInfo(内存态,重启即失)后进首页; + // 无效 getLoginInfo 返回 202,http 层统一 clearAuth + 踢回本页,此处无需分支 useDidShow(() => { - if (token && !loading) reLaunch(afterLoginUrl()) + if (!token || loading || restoring) return + setRestoring(true) + getLoginInfo() + .then((userInfo) => { + dispatch(setAuth({ token, userInfo })) + return reLaunch(afterLoginUrl()) + }) + .catch(() => { + // 202 分支 http 层已踢回本页(reLaunch 目标即本页,幂等);其余异常保持登录页可手动登录 + }) + .finally(() => setRestoring(false)) }) // 完整登录态校验与落地;未完成时推进授权态 @@ -61,7 +75,7 @@ export default function Login() { } const handleWxLogin = async () => { - if (loading) return + if (loading || restoring) return setLoading(true) try { applyResult(await performWxLogin()) @@ -102,7 +116,7 @@ export default function Login() { onClick={needAuth ? handleAuthUserInfo : handleWxLogin} > - {loading ? '登录中...' : needAuth ? '授权用户信息,继续' : '微信一键登录'} + {loading ? '登录中...' : restoring ? '恢复登录...' : needAuth ? '授权用户信息,继续' : '微信一键登录'} diff --git a/src/pages/scene/select.module.scss b/src/pages/scene/select.module.scss index dde5bb7..e148619 100644 --- a/src/pages/scene/select.module.scss +++ b/src/pages/scene/select.module.scss @@ -11,16 +11,26 @@ align-items: center; } -.list { margin-top: 20px; } +// 房间列表:整体一张圆角卡,行内分隔 +.list { + margin: 24px; + background: $color-bg-white; + border-radius: $radius-lg; + box-shadow: $shadow-card; + overflow: hidden; +} .item { - min-height: 104px; + min-height: 112px; padding: 16px 32px; - margin-bottom: 2px; - background: $color-bg-white; display: flex; align-items: center; justify-content: space-between; + border-bottom: 1px solid $color-border; + + &:last-of-type { + border-bottom: none; + } } // 名称 + 场景标签组:点击进编辑 @@ -50,20 +60,24 @@ color: $color-primary-deep; } +// 删除操作:胶囊浅红底,弱化破坏性但保持可识别 .del { flex-shrink: 0; margin-left: 24px; - font-size: 26px; + padding: 8px 24px; + background: $color-danger-light-bg; + border-radius: $radius-pill; + font-size: 24px; color: $color-danger; } // 添加房间:虚线边框卡,与家庭圈添加卡风格一致 .addBtn { - margin: 24px 24px 0; + margin: 24px; height: 104px; - border: 1px dashed $color-primary-light-border; + border: 2px dashed $color-primary-light-border; border-radius: $radius-md; - background: $color-bg-white; + background: $color-primary-light-bg; display: flex; align-items: center; justify-content: center; @@ -104,10 +118,16 @@ height: 88px; padding: 0 32px; background: $color-bg-gray; + border: 1px solid transparent; border-radius: $radius-md; font-size: 28px; color: $color-text; box-sizing: border-box; + + &:focus { + border-color: $color-primary; + background: $color-bg-white; + } } .senceGrid { diff --git a/src/types/global.d.ts b/src/types/global.d.ts index da9f065..6531856 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -2,3 +2,13 @@ declare module '*.module.scss' { const classes: { readonly [key: string]: string } export default classes } + +declare module '*.png' { + const src: string + export default src +} + +declare module '*.jpg' { + const src: string + export default src +}