diff --git a/src/pages/index/index.module.scss b/src/pages/index/index.module.scss index 217cdb8..6222c1d 100644 --- a/src/pages/index/index.module.scss +++ b/src/pages/index/index.module.scss @@ -1,46 +1,7 @@ -.page { position: relative; } - -.banner { - min-height: 180px; - background: linear-gradient(135deg, #1fca97 0%, #2dbf9c 100%); - padding: 48px 44px; - display: flex; - align-items: center; -} - -.bannerLeft { display: flex; flex-direction: column; } - -.bannerTitle { - font-size: 52px; - color: #ffffff; - font-weight: 700; - margin-bottom: 20px; -} - -.bannerSub { - font-size: 34px; - color: #e9fff8; - font-weight: 600; -} - -.siteRow { - height: 108px; - padding: 0 32px; - background: #ffffff; - display: flex; - align-items: center; - justify-content: space-between; -} - -.siteText { - font-size: 34px; - color: #222; - font-weight: 600; -} - -.siteArrow { - font-size: 36px; - color: #26c996; +.page { + min-height: 100vh; + background: $color-bg; + padding-bottom: 40px; } .stateWrap { @@ -54,7 +15,7 @@ margin-top: 40px; width: 240px; height: 80px; - background: #2ccb98; + background: $color-primary; border-radius: 40px; display: flex; align-items: center; @@ -63,86 +24,116 @@ font-size: 28px; } -.grid { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - padding: 24px; -} - -.card { - width: 48.5%; - margin-bottom: 24px; - padding: 36px 20px 28px; - box-sizing: border-box; - background: #ffffff; - border-radius: 24px; +// ===== 家庭圈入口 ===== +.familyEntry { + margin: 24px; + padding-left: 48px; display: flex; flex-direction: column; align-items: center; } -.icon { +.familyIcon { width: 108px; height: 108px; - border-radius: 50%; - background: linear-gradient(135deg, #1fca97 0%, #2dbf9c 100%); + border-radius: $radius-circle; + background: $color-primary-grad; display: flex; align-items: center; justify-content: center; - margin-bottom: 20px; } -.iconOffline { background: #eef0f4; } - -.iconText { - font-size: 36px; - color: #ffffff; +.familyEmoji { + font-size: 56px; + line-height: 1; } -.iconOffline .iconText { color: #b5b9c0; } - -.name { - max-width: 100%; - font-size: 28px; - font-weight: 600; - color: #2a2d33; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.statusRow { +.familyText { margin-top: 12px; + font-size: 28px; + color: $color-text; +} + +// ===== 未处理告警卡片 ===== +.unhandledCard { + position: relative; + margin: 0 24px 24px; + padding: 32px; + background: $color-bg-white; + border-radius: $radius-lg; + box-shadow: $shadow-card; +} + +.unhandledTag { + position: absolute; + top: -16px; + left: -8px; + padding: 6px 18px; + background: $color-danger-light-bg; + color: $color-danger-deep; + border: 2px solid $color-danger-deep; + border-radius: $radius-sm; + font-size: 22px; + transform: rotate(-4deg); + box-shadow: 0 2px 8px rgba(213, 59, 29, 0.15); +} + +.unhandledTitle { + font-size: 32px; + font-weight: 600; + color: $color-text; +} + +.unhandledRow { + margin-top: 16px; display: flex; align-items: center; } -.dot { - width: 14px; - height: 14px; - border-radius: 50%; - margin-right: 10px; +.rowLabel { + font-size: 28px; + color: $color-text-secondary; } -.dotOn { background: #2ccb98; } -.dotOff { background: #c0c4cc; } +.rowValue { + font-size: 28px; + color: $color-text; +} -.statusText { font-size: 24px; color: #12a37a; } -.statusTextOff { color: #9ea1a6; } +// ===== 告警消息列表 ===== +.messageList { + padding: 0 24px; +} -.fab { - position: fixed; - right: 40px; - bottom: 60px; - width: 112px; - height: 112px; - border-radius: 50%; - background: linear-gradient(135deg, #1fca97 0%, #2dbf9c 100%); +.messageCard { + margin-bottom: 24px; + padding: 32px; + background: $color-bg-white; + border-radius: $radius-lg; + box-shadow: $shadow-card; +} + +.messageHead { display: flex; align-items: center; - justify-content: center; - box-shadow: 0 8px 24px rgba(31, 202, 151, 0.4); } -.fabIcon { font-size: 48px; color: #ffffff; font-weight: 300; } +.messageDot { + width: 16px; + height: 16px; + border-radius: $radius-circle; + background: $color-danger-deep; + margin-right: 12px; +} + +.messageTitle { + font-size: 30px; + font-weight: 600; + color: $color-text; +} + +.messageTime { + margin-top: 12px; + font-size: 24px; + color: $color-text-secondary; +} diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx index 04db92e..311f253 100644 --- a/src/pages/index/index.tsx +++ b/src/pages/index/index.tsx @@ -1,105 +1,122 @@ -import { useState, useCallback } from 'react' +import { useState, useCallback, useMemo } from 'react' import { useDidShow } from '@tarojs/taro' import { View, Text } from '@tarojs/components' -import { useAppSelector, useAppDispatch } from '@/store/hooks' -import { setDeviceList, type Device } from '@/store/deviceSlice' import { Navbar } from '@/components/navbar' import { Loading } from '@/components/loading' import { Empty } from '@/components/empty' -import { getDeviceList } from '@/api/device' -import { DEVICE_ICON_MAP, type DeviceModel } from '@/constants/enums' +import { getEventList, type EventItem } from '@/api/event' import { ROUTES } from '@/constants/routes' import { navigateTo } from '@/utils/navigation' +import { showToast } from '@/utils/toast' +import { splitAlerts } from './splitAlerts' import styles from './index.module.scss' -const FALLBACK_ICON = '⌂' - export default function Index() { - const dispatch = useAppDispatch() - const scene = useAppSelector((s) => s.scene.current) - const deviceList = useAppSelector((s) => s.device.list) - + const [alertList, setAlertList] = useState([]) const [loading, setLoading] = useState(true) const [loadError, setLoadError] = useState(false) - // 在线状态需每次进入页面都新鲜(spec 明确):useDidShow 覆盖 - // 首次进入、从详情页 navigateBack 返回、tab 切换回三种场景,与 uni-app onShow 等价 - const loadDevices = useCallback(async () => { + // 每次进入页面重拉全量告警(与事件页"全部"筛选同参数) + const loadAlerts = useCallback(async () => { setLoading(true) setLoadError(false) try { - const data = await getDeviceList(scene?.id ?? null) - dispatch(setDeviceList(data)) + const list = await getEventList({ sceneId: 0, status: 0, startDate: '', endDate: '' }) + setAlertList(list) } catch { setLoadError(true) } finally { setLoading(false) } - }, [dispatch, scene?.id]) + }, []) useDidShow(() => { - loadDevices() + loadAlerts() }) - const goDetail = (device: Device) => { - const name = encodeURIComponent(device.name) - navigateTo(`${ROUTES.DEVICE_DETAIL}?id=${device.id}&name=${name}&model=${device.model}&online=${device.online ? 1 : 0}`) - } - const goAdd = () => navigateTo(ROUTES.DEVICE_ADD) - const goSceneSelect = () => navigateTo(ROUTES.SCENE_SELECT) + const { unhandled, messages } = useMemo(() => splitAlerts(alertList), [alertList]) - const iconOf = (model: DeviceModel) => DEVICE_ICON_MAP[model] ?? FALLBACK_ICON + const goFamily = () => navigateTo(ROUTES.FAMILY) + const goAlertDetail = () => showToast('告警详情即将上线') return ( - - - - - 智慧养老云助手 - 一键开启智慧好生活 - - - - - {scene?.name || '请选择场所'} - › - + {loading ? ( ) : loadError ? ( - 重试 - - ) : deviceList.length === 0 ? ( - - - 添加设备 + 重试 ) : ( - - {deviceList.map((device) => ( - goDetail(device)}> - - {iconOf(device.model)} - - {device.name} - - - - {device.online ? '在线' : '离线'} - - - - ))} - - )} + <> + - - + - + {unhandled.length > 0 && ( + + )} + + {messages.length > 0 && ( + + {messages.map((alert) => ( + + ))} + + )} + + {unhandled.length === 0 && messages.length === 0 && ( + + + + )} + + )} + + ) +} + +function FamilyEntry({ onClick }: { onClick: () => void }) { + return ( + + + 👨‍👩‍👧 + + 家庭圈 + + ) +} + +function UnhandledAlert({ alert, onClick }: { alert: EventItem; onClick: () => void }) { + return ( + + 未处理 + {alert.deviceName || '未知设备'} + {alert.sceneName && ( + + 家庭圈: + {alert.sceneName} + + )} + {alert.time && ( + + 时间: + {alert.time} + + )} + + ) +} + +function AlertMessageItem({ alert, onClick }: { alert: EventItem; onClick: () => void }) { + return ( + + + + 告警消息 + + {alert.time && {alert.time}} ) }