feat: 完善我的设备入口页

This commit is contained in:
czz
2026-05-08 14:46:31 +08:00
parent 9885f131da
commit 9a2f382dc9
5 changed files with 364 additions and 23 deletions

View File

@@ -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 台设备在线", "权限待确认"]
);
});

View File

@@ -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;
}

View File

@@ -1,3 +1,233 @@
.devices-page { .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%);
} }

View File

@@ -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"; import "./index.scss";
const sections: SecondaryPageSection[] = [ const deviceCards = getDeviceCards();
{
title: "设备概览",
items: [
{ label: "已绑定设备", value: "0 台" },
{ label: "设备共享管理", value: "待接入" },
{ label: "添加设备", value: "可复用首页绑定能力" }
]
},
{
title: "当前状态",
items: [{ label: "设备列表", value: "暂无已绑定设备" }]
}
];
export default function DevicesPage() { export default function DevicesPage() {
const handleCardClick = (card: DeviceCard) => {
Taro.showToast({
title: `${card.title}页面待完善`,
icon: "none"
});
};
return ( return (
<SecondaryPage <View className="devices-page">
eyebrow="DEVICES" <View className="devices-page__halo devices-page__halo--large" />
title="我的设备" <View className="devices-page__halo devices-page__halo--small" />
description="这里先保留设备管理骨架,后续可以接真实设备列表、设备状态和共享管理。"
sections={sections} <View className="devices-page__content">
footerTip="如果后面要对接设备绑定,可以优先复用首页已经实现的扫码和蓝牙入口。" {deviceCards.map((card) => (
/> <View
className="devices-page__card"
key={card.key}
hoverClass="devices-page__card--hover"
hoverStartTime={20}
hoverStayTime={120}
onClick={() => handleCardClick(card)}
>
<View className="devices-page__copy">
<Text className="devices-page__title">{card.title}</Text>
<Text className="devices-page__summary">{card.summary}</Text>
<View className={`devices-page__status devices-page__status--${card.statusTone}`}>
<View className="devices-page__status-dot" />
<Text className="devices-page__status-text">{card.statusText}</Text>
</View>
<Text className="devices-page__action">{card.actionText}</Text>
</View>
<View className={`devices-page__icon devices-page__icon--${card.icon}`} />
</View>
))}
</View>
</View>
); );
} }

View File

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