From 9a2f382dc98d9c3a515842342a6412200e4caf23 Mon Sep 17 00:00:00 2001 From: czz <862977248@qq.com> Date: Fri, 8 May 2026 14:46:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E6=88=91=E7=9A=84?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E5=85=A5=E5=8F=A3=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/tests/devices-page.test.cjs | 30 ++++ src/pages/devices/device-data.ts | 49 ++++++ src/pages/devices/index.scss | 232 +++++++++++++++++++++++++++- src/pages/devices/index.tsx | 64 +++++--- tsconfig.devices-tests.json | 12 ++ 5 files changed, 364 insertions(+), 23 deletions(-) create mode 100644 scripts/tests/devices-page.test.cjs create mode 100644 src/pages/devices/device-data.ts create mode 100644 tsconfig.devices-tests.json diff --git a/scripts/tests/devices-page.test.cjs b/scripts/tests/devices-page.test.cjs new file mode 100644 index 0000000..04fa289 --- /dev/null +++ b/scripts/tests/devices-page.test.cjs @@ -0,0 +1,30 @@ +const assert = require("node:assert/strict"); +const { getDeviceCards } = require("../../tmp/devices-tests/device-data.js"); + +function run(name, fn) { + try { + fn(); + console.log(`PASS ${name}`); + } catch (error) { + console.error(`FAIL ${name}`); + throw error; + } +} + +run("getDeviceCards returns the three expected device groups in order", () => { + const cards = getDeviceCards(); + + assert.deepEqual( + cards.map((item) => item.key), + ["vitals", "smart-bed", "ai-camera"] + ); +}); + +run("getDeviceCards exposes display-ready status copy for each card", () => { + const cards = getDeviceCards(); + + assert.deepEqual( + cards.map((item) => item.statusText), + ["未绑定设备", "1 台设备在线", "权限待确认"] + ); +}); diff --git a/src/pages/devices/device-data.ts b/src/pages/devices/device-data.ts new file mode 100644 index 0000000..8648c36 --- /dev/null +++ b/src/pages/devices/device-data.ts @@ -0,0 +1,49 @@ +export type DeviceCardTone = "muted" | "online" | "warning"; + +export type DeviceCardKey = "vitals" | "smart-bed" | "ai-camera"; + +export type DeviceCardIcon = "vitals" | "bed" | "camera"; + +export type DeviceCard = { + key: DeviceCardKey; + title: string; + summary: string; + statusText: string; + statusTone: DeviceCardTone; + actionText: string; + icon: DeviceCardIcon; +}; + +const deviceCards: DeviceCard[] = [ + { + key: "vitals", + title: "体征监测设备", + summary: "支持心率、血氧、血压与呼吸等关键体征监测。", + statusText: "未绑定设备", + statusTone: "muted", + actionText: "点击查看设备专区", + icon: "vitals" + }, + { + key: "smart-bed", + title: "智能床 / 床垫", + summary: "覆盖睡眠监测、翻身检测、离床提醒与睡眠质量分析。", + statusText: "1 台设备在线", + statusTone: "online", + actionText: "点击查看设备专区", + icon: "bed" + }, + { + key: "ai-camera", + title: "AI 摄像头", + summary: "可用于跌倒识别、异常行为预警与实时监护联动。", + statusText: "权限待确认", + statusTone: "warning", + actionText: "点击查看设备专区", + icon: "camera" + } +]; + +export function getDeviceCards() { + return deviceCards; +} diff --git a/src/pages/devices/index.scss b/src/pages/devices/index.scss index 2971b8b..5307a8b 100644 --- a/src/pages/devices/index.scss +++ b/src/pages/devices/index.scss @@ -1,3 +1,233 @@ .devices-page { - display: block; + position: relative; + min-height: 100vh; + padding: 26rpx 24rpx 44rpx; + box-sizing: border-box; + background: linear-gradient( + 180deg, + var(--color-bg-page-gradient-start) 0%, + var(--color-bg-page) 42%, + var(--color-bg-page-gradient-end) 100% + ); + overflow: hidden; +} + +.devices-page__halo { + position: absolute; + border-radius: 50%; + pointer-events: none; +} + +.devices-page__halo--large { + top: -100rpx; + right: -20rpx; + width: 360rpx; + height: 360rpx; + background: + radial-gradient(circle, rgba(54, 228, 170, 0.06) 0 24%, transparent 25% 42%, rgba(104, 116, 146, 0.18) 43% 62%, transparent 63%), + radial-gradient(circle at center, rgba(255, 255, 255, 0.05), transparent 72%); +} + +.devices-page__halo--small { + top: 140rpx; + right: 110rpx; + width: 170rpx; + height: 120rpx; + background: radial-gradient(circle at center, rgba(255, 255, 255, 0.04), transparent 70%); +} + +.devices-page__content { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + gap: 20rpx; +} + +.devices-page__card { + position: relative; + min-height: 280rpx; + padding: 28rpx 32rpx; + border-radius: 48rpx; + background: + linear-gradient(180deg, rgba(43, 50, 68, 0.98) 0%, rgba(39, 45, 62, 0.98) 100%), + var(--color-bg-surface); + box-shadow: + inset 0 0 0 2rpx var(--color-border-light), + var(--shadow-surface); + overflow: hidden; + transform: scale(1); + transition: transform 180ms ease, box-shadow 180ms ease, filter 180ms ease; +} + +.devices-page__card::before { + position: absolute; + inset: 0; + content: ""; + background: linear-gradient(90deg, rgba(255, 255, 255, 0.04), transparent 30%); + pointer-events: none; +} + +.devices-page__card--hover { + transform: scale(0.985); + filter: brightness(1.05); + box-shadow: + inset 0 0 0 2rpx rgba(255, 255, 255, 0.06), + 0 18rpx 36rpx rgba(4, 10, 24, 0.26); +} + +.devices-page__copy { + position: relative; + z-index: 2; + display: flex; + flex-direction: column; + align-items: flex-start; + max-width: 430rpx; + min-height: 224rpx; +} + +.devices-page__title { + color: var(--color-text-white); + font-size: 38rpx; + font-weight: 600; + line-height: 1.3; +} + +.devices-page__summary { + margin-top: 20rpx; + color: var(--color-text-secondary); + font-size: 24rpx; + line-height: 1.7; +} + +.devices-page__status { + display: inline-flex; + align-items: center; + margin-top: 22rpx; + padding: 10rpx 18rpx; + border-radius: 999rpx; + background: rgba(255, 255, 255, 0.06); +} + +.devices-page__status--online { + background: rgba(54, 228, 170, 0.12); +} + +.devices-page__status--warning { + background: rgba(255, 157, 87, 0.14); +} + +.devices-page__status-dot { + width: 12rpx; + height: 12rpx; + margin-right: 12rpx; + border-radius: 50%; + background: var(--color-text-muted); + box-shadow: 0 0 0 6rpx rgba(255, 255, 255, 0.02); +} + +.devices-page__status--online .devices-page__status-dot { + background: var(--color-text-accent); + box-shadow: 0 0 0 6rpx rgba(54, 228, 170, 0.08); +} + +.devices-page__status--warning .devices-page__status-dot { + background: var(--color-warning-main); + box-shadow: 0 0 0 6rpx rgba(255, 157, 87, 0.08); +} + +.devices-page__status-text { + color: var(--color-text-white); + font-size: 22rpx; +} + +.devices-page__action { + margin-top: auto; + color: var(--color-text-muted); + font-size: 22rpx; + line-height: 1.5; +} + +.devices-page__icon { + position: absolute; + right: 18rpx; + bottom: 4rpx; + width: 260rpx; + height: 200rpx; + opacity: 0.14; +} + +.devices-page__icon::before, +.devices-page__icon::after { + position: absolute; + content: ""; + box-sizing: border-box; +} + +.devices-page__icon--vitals::before { + left: 26rpx; + top: 26rpx; + width: 126rpx; + height: 126rpx; + border: 18rpx solid rgba(255, 255, 255, 0.72); + border-right-color: transparent; + border-bottom-color: transparent; + border-radius: 70rpx 70rpx 0 0; + transform: rotate(-45deg); +} + +.devices-page__icon--vitals::after { + left: 58rpx; + top: 98rpx; + width: 148rpx; + height: 20rpx; + border-radius: 999rpx; + background: rgba(18, 26, 39, 0.78); + box-shadow: + -48rpx -28rpx 0 -4rpx rgba(18, 26, 39, 0.78), + -22rpx -2rpx 0 -4rpx rgba(18, 26, 39, 0.78), + 38rpx 0 0 -4rpx rgba(18, 26, 39, 0.78); + transform: rotate(0deg); +} + +.devices-page__icon--bed::before { + left: 36rpx; + right: 22rpx; + bottom: 42rpx; + height: 72rpx; + border-radius: 34rpx 34rpx 30rpx 30rpx; + background: rgba(255, 255, 255, 0.68); + transform: skewX(-18deg); +} + +.devices-page__icon--bed::after { + left: 62rpx; + right: 44rpx; + top: 28rpx; + height: 88rpx; + border-radius: 48rpx 48rpx 38rpx 38rpx; + border: 14rpx solid rgba(18, 26, 39, 0.75); + border-left-width: 0; + border-bottom-width: 18rpx; + transform: rotate(12deg); +} + +.devices-page__icon--camera::before { + left: 70rpx; + top: 18rpx; + width: 128rpx; + height: 128rpx; + border-radius: 50%; + background: rgba(255, 255, 255, 0.7); + box-shadow: inset 0 0 0 26rpx rgba(18, 26, 39, 0.72); +} + +.devices-page__icon--camera::after { + left: 74rpx; + bottom: 28rpx; + width: 122rpx; + height: 68rpx; + border-radius: 18rpx 18rpx 30rpx 30rpx; + background: rgba(255, 255, 255, 0.62); + clip-path: polygon(18% 0, 82% 0, 100% 100%, 0 100%); } diff --git a/src/pages/devices/index.tsx b/src/pages/devices/index.tsx index 4126261..7b2a01e 100644 --- a/src/pages/devices/index.tsx +++ b/src/pages/devices/index.tsx @@ -1,29 +1,49 @@ -import SecondaryPage, { type SecondaryPageSection } from "../../components/secondary-page"; +import { Text, View } from "@tarojs/components"; +import Taro from "@tarojs/taro"; +import { getDeviceCards, type DeviceCard } from "./device-data"; import "./index.scss"; -const sections: SecondaryPageSection[] = [ - { - title: "设备概览", - items: [ - { label: "已绑定设备", value: "0 台" }, - { label: "设备共享管理", value: "待接入" }, - { label: "添加设备", value: "可复用首页绑定能力" } - ] - }, - { - title: "当前状态", - items: [{ label: "设备列表", value: "暂无已绑定设备" }] - } -]; +const deviceCards = getDeviceCards(); export default function DevicesPage() { + const handleCardClick = (card: DeviceCard) => { + Taro.showToast({ + title: `${card.title}页面待完善`, + icon: "none" + }); + }; + return ( - + + + + + + {deviceCards.map((card) => ( + handleCardClick(card)} + > + + {card.title} + {card.summary} + + + + {card.statusText} + + + {card.actionText} + + + + + ))} + + ); } diff --git a/tsconfig.devices-tests.json b/tsconfig.devices-tests.json new file mode 100644 index 0000000..18b48b7 --- /dev/null +++ b/tsconfig.devices-tests.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "outDir": "tmp/devices-tests", + "module": "CommonJS", + "target": "ES2020", + "declaration": false, + "types": [] + }, + "include": ["src/pages/devices/device-data.ts"] +}