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"]
+}