refactor: 收口公共AppBar组件

This commit is contained in:
czz
2026-05-09 15:55:05 +08:00
parent f3eb25b035
commit b7fa2fce9d
25 changed files with 497 additions and 242 deletions

11
src/assets/svg/add.svg Normal file
View File

@@ -0,0 +1,11 @@
<svg viewBox="0 0 37.1797 37.1798" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="37.179688" height="37.179787" fill="none" customFrame="#000000">
<g id="组 9850">
<g id="组 9848">
<circle id="椭圆 1364" cx="18.5898952" cy="18.5898952" r="17.5898952" stroke="rgb(255,255,255)" stroke-linecap="round" stroke-linejoin="round" stroke-width="0.000000" />
</g>
<g id="组 9849">
<line id="直线 573" x1="10.6748047" x2="26.5057106" y1="18.5898972" y2="18.5898972" stroke="rgb(255,255,255)" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.000000" />
<line id="直线 574" x1="0" x2="15.830905" y1="0" y2="0" stroke="rgb(255,255,255)" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.000000" transform="matrix(0,1,-1,0,18.5898,10.6744)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 841 B

6
src/assets/svg/back.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg viewBox="0 0 42 42" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="42.000000" height="42.000000" fill="none" customFrame="#000000">
<g id="组 10388">
<rect id="矩形 3479" width="42.000000" height="42.000000" x="0.000000" y="0.000000" />
<path id="路径 15460" d="M28.2715 6.45681L13.7285 21L28.2715 35.5432" stroke="rgb(255,255,255)" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.000000" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 473 B

View File

@@ -0,0 +1,65 @@
.app-bar {
position: relative;
z-index: 10;
width: 100%;
box-sizing: border-box;
padding-top: var(--appbar-top-inset);
}
.app-bar__row {
width: 100%;
min-height: var(--appbar-menu-height);
display: flex;
align-items: center;
box-sizing: border-box;
}
.app-bar__side {
width: var(--appbar-capsule-safe-width);
min-width: var(--appbar-capsule-safe-width);
display: flex;
align-items: center;
}
.app-bar__side--left {
justify-content: flex-start;
}
.app-bar__side--right {
justify-content: flex-end;
}
.app-bar__content {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.app-bar__title {
color: #f3f7ff;
font-size: 32rpx;
font-weight: 600;
line-height: 1.4;
}
.app-bar__action {
min-width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
}
.app-bar__action--placeholder {
opacity: 0;
}
.app-bar__back-icon {
width: 32rpx;
height: 32rpx;
display: block;
flex-shrink: 0;
}

View File

@@ -0,0 +1,62 @@
import { Image, Text, View } from "@tarojs/components";
import type { CSSProperties } from "react";
import { useEffect, useState } from "react";
import backIcon from "../../assets/svg/back.svg";
import { getAppBarMetrics } from "../../utils/app-bar";
import type { AppBarMetrics } from "../../utils/app-bar-metrics";
import "./index.scss";
type AppBarProps = {
title: string;
showBack?: boolean;
onBack?: () => void;
};
const defaultMetrics: AppBarMetrics = {
topInset: 0,
menuTop: 6,
menuHeight: 32,
capsuleSafeWidth: 96
};
export default function AppBar({
title,
showBack = false,
onBack
}: AppBarProps) {
const [metrics, setMetrics] = useState<AppBarMetrics>(defaultMetrics);
useEffect(() => {
setMetrics(getAppBarMetrics());
}, []);
const style = {
"--appbar-top-inset": `${metrics.topInset}px`,
"--appbar-menu-height": `${metrics.menuHeight}px`,
"--appbar-capsule-safe-width": `${metrics.capsuleSafeWidth}px`
} as CSSProperties;
return (
<View className="app-bar" style={style}>
<View className="app-bar__row">
<View className="app-bar__side app-bar__side--left">
{showBack ? (
<View className="app-bar__action" onClick={onBack}>
<Image className="app-bar__back-icon" src={backIcon} mode="aspectFit" />
</View>
) : (
<View className="app-bar__action app-bar__action--placeholder" />
)}
</View>
<View className="app-bar__content">
<Text className="app-bar__title">{title}</Text>
</View>
<View className="app-bar__side app-bar__side--right">
<View className="app-bar__action app-bar__action--placeholder" />
</View>
</View>
</View>
);
}

View File

@@ -1,4 +1,6 @@
import { Text, View } from "@tarojs/components";
import { navigateBackWithFallback } from "../../utils/app-bar";
import AppBar from "../app-bar";
import "./index.scss";
export type SecondaryPageItem = {
@@ -27,9 +29,10 @@ export default function SecondaryPage(props: SecondaryPageProps) {
<View className="secondary-page__halo secondary-page__halo--large" />
<View className="secondary-page__halo secondary-page__halo--small" />
<AppBar title={title} showBack onBack={() => navigateBackWithFallback("/pages/mine/index")} />
<View className="secondary-page__hero">
<Text className="secondary-page__eyebrow">{eyebrow}</Text>
<Text className="secondary-page__title">{title}</Text>
<Text className="secondary-page__description">{description}</Text>
</View>

View File

@@ -1,7 +1,7 @@
.devices-page {
position: relative;
min-height: 100vh;
padding: 26rpx 24rpx 44rpx;
padding: 0 24rpx 44rpx;
box-sizing: border-box;
background: linear-gradient(
180deg,
@@ -36,6 +36,16 @@
background: radial-gradient(circle at center, rgba(255, 255, 255, 0.04), transparent 70%);
}
.devices-page__subtitle {
position: relative;
z-index: 1;
display: block;
margin: 12rpx 0 24rpx;
color: var(--color-text-secondary);
font-size: 24rpx;
line-height: 1.6;
}
.devices-page__content {
position: relative;
z-index: 1;

View File

@@ -1,5 +1,7 @@
import { Text, View } from "@tarojs/components";
import Taro from "@tarojs/taro";
import AppBar from "../../components/app-bar";
import { navigateBackWithFallback } from "../../utils/app-bar";
import { getDeviceCards, type DeviceCard } from "./device-data";
import "./index.scss";
@@ -18,6 +20,9 @@ export default function DevicesPage() {
<View className="devices-page__halo devices-page__halo--large" />
<View className="devices-page__halo devices-page__halo--small" />
<AppBar title="我的设备" showBack onBack={() => navigateBackWithFallback("/pages/mine/index")} />
<Text className="devices-page__subtitle"></Text>
<View className="devices-page__content">
{deviceCards.map((card) => (
<View

View File

@@ -1,7 +1,7 @@
.device-page {
position: relative;
min-height: 100vh;
padding: calc(var(--top-safe-height, 0px) + 24rpx) 24rpx 156rpx;
padding: 0 24rpx 156rpx;
box-sizing: border-box;
background: linear-gradient(180deg, var(--color-bg-page-gradient-start) 0%, var(--color-bg-page-gradient-end) 100%);
overflow: hidden;
@@ -34,40 +34,48 @@
.device-header {
position: relative;
z-index: 1;
width: 100%;
padding-right: calc(var(--menu-safe-width, 0px) + 12rpx);
margin-bottom: 8rpx;
min-height: calc((var(--menu-top, 0px) - var(--top-safe-height, 0px) - 24rpx) + var(--menu-height, 32px) + 36rpx + 6rpx);
}
.device-header__top-actions {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
margin: 12rpx 0 18rpx;
}
.device-header__login {
position: absolute;
left: 0;
top: calc(var(--menu-top, 0px) - var(--top-safe-height, 0px) - 24rpx);
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 92rpx;
height: var(--menu-height, 32px);
min-height: 56rpx;
padding: 0 24rpx;
border-radius: 999rpx;
background: linear-gradient(90deg, var(--color-brand-start) 0%, var(--color-brand-end) 100%);
color: var(--color-text-white);
font-size: 22rpx;
line-height: var(--menu-height, 32px);
text-align: center;
box-shadow: 0 12rpx 24rpx var(--color-brand-shadow);
}
.device-header__add {
position: absolute;
right: calc(var(--menu-safe-width, 0px) + 2rpx);
top: calc((var(--menu-top, 0px) - var(--top-safe-height, 0px) - 24rpx) + var(--menu-height, 32px) + 6rpx);
display: inline-flex;
align-items: center;
justify-content: center;
width: 36rpx;
height: 36rpx;
border: 2rpx solid var(--color-border-strong);
border-radius: 50%;
}
.device-header__login-text {
color: var(--color-text-white);
font-size: 22rpx;
}
.device-header__add-text {
color: var(--color-text-white);
font-size: 30rpx;
line-height: 30rpx;
text-align: center;
line-height: 1;
}
.device-summary {

View File

@@ -1,8 +1,8 @@
import { Text, View } from "@tarojs/components";
import Taro from "@tarojs/taro";
import type { CSSProperties } from "react";
import { useEffect, useRef, useState } from "react";
import ActionButton from "../../components/action-button";
import AppBar from "../../components/app-bar";
import BottomTabbar from "../../components/bottom-tabbar";
import PanelCard from "../../components/panel-card";
import { createMainTabItems, handleMainTabNavigation } from "../../utils/main-tabbar";
@@ -49,24 +49,9 @@ function parseDeviceCode(result?: string) {
export default function Index() {
const [deviceCount, setDeviceCount] = useState(0);
const [bluetoothStatus, setBluetoothStatus] = useState<BluetoothStatus>("idle");
const [topSafeHeight, setTopSafeHeight] = useState(0);
const [menuSafeWidth, setMenuSafeWidth] = useState(0);
const [menuTop, setMenuTop] = useState(0);
const [menuHeight, setMenuHeight] = useState(0);
const discoveryTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
const windowInfo = typeof Taro.getWindowInfo === "function" ? Taro.getWindowInfo() : Taro.getSystemInfoSync();
const menuButtonRect =
typeof Taro.getMenuButtonBoundingClientRect === "function" ? Taro.getMenuButtonBoundingClientRect() : null;
const safeTop = menuButtonRect?.bottom || windowInfo.statusBarHeight || 0;
const safeRight = menuButtonRect ? windowInfo.windowWidth - menuButtonRect.left : 0;
setTopSafeHeight(safeTop);
setMenuSafeWidth(safeRight);
setMenuTop(menuButtonRect?.top || Math.max((windowInfo.statusBarHeight || 0) + 6, 0));
setMenuHeight(menuButtonRect?.height || 32);
return () => {
if (discoveryTimerRef.current) {
clearTimeout(discoveryTimerRef.current);
@@ -236,25 +221,19 @@ export default function Index() {
}
};
const pageStyle = {
"--top-safe-height": `${topSafeHeight}px`,
"--menu-safe-width": `${menuSafeWidth}px`,
"--menu-top": `${menuTop}px`,
"--menu-height": `${menuHeight}px`
} as CSSProperties;
return (
<View className="device-page" style={pageStyle}>
<View className="device-page">
<View className="device-page__halo device-page__halo--large" />
<View className="device-page__halo device-page__halo--small" />
<View className="device-header">
<AppBar title="" />
<View className="device-header__top-actions">
<View className="device-header__login" onClick={handleLogin}>
<Text className="device-header__login-text"></Text>
</View>
<View className="device-header__add" onClick={handleAdd}>
+
<Text className="device-header__add-text">+</Text>
</View>
</View>

View File

@@ -1,7 +1,7 @@
.message-page {
position: relative;
min-height: 100vh;
padding: calc(var(--top-safe-height, 0px) + 24rpx) 24rpx 156rpx;
padding: 0 24rpx 156rpx;
box-sizing: border-box;
background: linear-gradient(180deg, #1d2331 0%, #171d29 100%);
overflow: hidden;
@@ -29,37 +29,39 @@
background: radial-gradient(circle, rgba(86, 116, 184, 0.12), transparent 72%);
}
.message-header {
.message-tabs {
position: relative;
z-index: 1;
width: 100%;
padding-right: calc(var(--menu-safe-width, 0px) + 12rpx);
margin-bottom: 10rpx;
box-sizing: border-box;
min-height: calc((var(--menu-top, 0px) - var(--top-safe-height, 0px) - 24rpx) + var(--menu-height, 32px) + 24rpx);
}
.message-tabs {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 110rpx;
min-height: inherit;
padding: 0;
margin-top: 12rpx;
margin-bottom: 24rpx;
}
.message-tabs__item {
position: relative;
flex: 1;
height: 64rpx;
min-width: 0;
display: flex;
align-items: center;
justify-content: center;
min-width: 120rpx;
}
.message-tabs__content {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8rpx;
}
.message-tabs__label {
color: #8f97ac;
font-size: 24rpx;
line-height: 1.3;
text-align: center;
}
.message-tabs__label--active {
@@ -68,13 +70,12 @@
}
.message-tabs__dot {
position: absolute;
top: 6rpx;
right: -10rpx;
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background: #ff4d4f;
flex-shrink: 0;
transform: translateY(-8rpx);
}
.message-tabs__line {

View File

@@ -1,7 +1,6 @@
import { Text, View } from "@tarojs/components";
import Taro from "@tarojs/taro";
import type { CSSProperties } from "react";
import { useEffect, useState } from "react";
import { useState } from "react";
import AppBar from "../../components/app-bar";
import BottomTabbar from "../../components/bottom-tabbar";
import { createMainTabItems, handleMainTabNavigation } from "../../utils/main-tabbar";
import "./index.scss";
@@ -87,54 +86,32 @@ const fieldLabels = {
export default function MessagePage() {
const [activeTab, setActiveTab] = useState<MessageTabKey>("vital");
const [topSafeHeight, setTopSafeHeight] = useState(0);
const [menuSafeWidth, setMenuSafeWidth] = useState(0);
const [menuTop, setMenuTop] = useState(0);
const [menuHeight, setMenuHeight] = useState(0);
const currentMessages = messageList.filter((item) => item.category === activeTab);
useEffect(() => {
const windowInfo = typeof Taro.getWindowInfo === "function" ? Taro.getWindowInfo() : Taro.getSystemInfoSync();
const menuButtonRect =
typeof Taro.getMenuButtonBoundingClientRect === "function" ? Taro.getMenuButtonBoundingClientRect() : null;
const safeTop = menuButtonRect?.bottom || windowInfo.statusBarHeight || 0;
const safeRight = menuButtonRect ? windowInfo.windowWidth - menuButtonRect.left : 0;
setTopSafeHeight(safeTop);
setMenuSafeWidth(safeRight);
setMenuTop(menuButtonRect?.top || Math.max((windowInfo.statusBarHeight || 0) + 6, 0));
setMenuHeight(menuButtonRect?.height || 32);
}, []);
const navItems = createMainTabItems("message");
const pageStyle = {
"--top-safe-height": `${topSafeHeight}px`,
"--menu-safe-width": `${menuSafeWidth}px`,
"--menu-top": `${menuTop}px`,
"--menu-height": `${menuHeight}px`
} as CSSProperties;
return (
<View className="message-page" style={pageStyle}>
<View className="message-page">
<View className="message-page__glow message-page__glow--top" />
<View className="message-page__glow message-page__glow--side" />
<View className="message-header">
<View className="message-tabs">
{tabs.map((item) => {
const isActive = item.key === activeTab;
<AppBar title="" />
return (
<View className="message-tabs__item" key={item.key} onClick={() => setActiveTab(item.key)}>
<View className="message-tabs">
{tabs.map((item) => {
const isActive = item.key === activeTab;
return (
<View className="message-tabs__item" key={item.key} onClick={() => setActiveTab(item.key)}>
<View className="message-tabs__content">
<Text className={`message-tabs__label ${isActive ? "message-tabs__label--active" : ""}`}>{item.label}</Text>
{item.hasDot ? <View className="message-tabs__dot" /> : null}
{isActive ? <View className="message-tabs__line" /> : null}
</View>
);
})}
</View>
{isActive ? <View className="message-tabs__line" /> : null}
</View>
);
})}
</View>
<View className="message-list">

View File

@@ -1,7 +1,7 @@
.mine-page {
position: relative;
min-height: 100vh;
padding: calc(var(--top-safe-height, 0px) + 34rpx) 24rpx 170rpx;
padding: 0 24rpx 170rpx;
box-sizing: border-box;
background: linear-gradient(180deg, #1f2534 0%, #171d29 100%);
overflow: hidden;
@@ -31,6 +31,14 @@
background: radial-gradient(circle at center, rgba(255, 255, 255, 0.04), transparent 70%);
}
.mine-page__top-actions {
position: relative;
z-index: 1;
display: flex;
justify-content: flex-end;
margin: 12rpx 0 18rpx;
}
.mine-page__header-card {
position: relative;
z-index: 1;
@@ -132,7 +140,6 @@
flex-direction: column;
align-items: flex-end;
padding-left: 18rpx;
padding-right: calc(var(--menu-safe-width, 0px) + 6rpx);
}
.mine-page__icon-actions {

View File

@@ -1,7 +1,6 @@
import { Text, View } from "@tarojs/components";
import Taro from "@tarojs/taro";
import type { CSSProperties } from "react";
import { useEffect, useState } from "react";
import AppBar from "../../components/app-bar";
import BottomTabbar from "../../components/bottom-tabbar";
import { createMainTabItems, handleMainTabNavigation } from "../../utils/main-tabbar";
import "./index.scss";
@@ -40,20 +39,6 @@ const featureItems: FeatureItem[] = [
];
export default function MinePage() {
const [topSafeHeight, setTopSafeHeight] = useState(0);
const [menuSafeWidth, setMenuSafeWidth] = useState(0);
useEffect(() => {
const windowInfo = typeof Taro.getWindowInfo === "function" ? Taro.getWindowInfo() : Taro.getSystemInfoSync();
const menuButtonRect =
typeof Taro.getMenuButtonBoundingClientRect === "function" ? Taro.getMenuButtonBoundingClientRect() : null;
const safeTop = menuButtonRect?.bottom || windowInfo.statusBarHeight || 0;
const safeRight = menuButtonRect ? windowInfo.windowWidth - menuButtonRect.left : 0;
setTopSafeHeight(safeTop);
setMenuSafeWidth(safeRight);
}, []);
const showToast = (title: string) => {
Taro.showToast({
title,
@@ -76,16 +61,19 @@ export default function MinePage() {
const tabItems = createMainTabItems("mine");
const pageStyle = {
"--top-safe-height": `${topSafeHeight}px`,
"--menu-safe-width": `${menuSafeWidth}px`
} as CSSProperties;
return (
<View className="mine-page" style={pageStyle}>
<View className="mine-page">
<View className="mine-page__halo mine-page__halo--large" />
<View className="mine-page__halo mine-page__halo--small" />
<AppBar title="" />
<View className="mine-page__top-actions">
<View className="mine-page__icon-actions">
<View className="mine-page__circle-action mine-page__circle-action--support" onClick={() => openPage("/pages/support/index")} />
<View className="mine-page__circle-action mine-page__circle-action--settings" onClick={() => openPage("/pages/settings/index")} />
</View>
</View>
<View className="mine-page__header-card">
<View className="mine-page__header-main">
<View className="mine-page__avatar">
@@ -111,11 +99,6 @@ export default function MinePage() {
</View>
<View className="mine-page__header-actions">
<View className="mine-page__icon-actions">
<View className="mine-page__circle-action mine-page__circle-action--support" onClick={() => openPage("/pages/support/index")} />
<View className="mine-page__circle-action mine-page__circle-action--settings" onClick={() => openPage("/pages/settings/index")} />
</View>
<Text className="mine-page__profile-link" onClick={() => openPage("/pages/profile/index")}>
</Text>

View File

@@ -1,7 +1,7 @@
.repair-detail-page {
position: relative;
min-height: 100vh;
padding: 28rpx 24rpx 72rpx;
padding: 0 24rpx 72rpx;
box-sizing: border-box;
background: linear-gradient(180deg, #0b1220 0%, #121a2c 100%);
overflow: hidden;
@@ -36,6 +36,16 @@
z-index: 1;
}
.repair-detail-page__header-note {
position: relative;
z-index: 1;
display: block;
margin-top: 12rpx;
color: rgba(255, 255, 255, 0.58);
font-size: 24rpx;
text-align: center;
}
.repair-detail-page__hero {
display: flex;
flex-direction: column;

View File

@@ -1,5 +1,7 @@
import { Button, Text, View } from "@tarojs/components";
import Taro from "@tarojs/taro";
import AppBar from "../../components/app-bar";
import { navigateBackWithFallback } from "../../utils/app-bar";
import {
REPAIR_DEVICE_TYPE_LABELS,
REPAIR_DRAFT_STORAGE_KEY,
@@ -30,6 +32,9 @@ export default function RepairDetailPage() {
<View className="repair-detail-page__glow repair-detail-page__glow--left" />
<View className="repair-detail-page__glow repair-detail-page__glow--right" />
<AppBar title="报修详情" showBack onBack={() => navigateBackWithFallback("/pages/repair/index")} />
<Text className="repair-detail-page__header-note"></Text>
<View className="repair-detail-page__hero">
<View className="repair-detail-page__success-ring">
<View className="repair-detail-page__success-check" />
@@ -91,7 +96,7 @@ export default function RepairDetailPage() {
</View>
</View>
<Button className="repair-detail-page__button" onClick={() => Taro.navigateBack()}>
<Button className="repair-detail-page__button" onClick={() => navigateBackWithFallback("/pages/repair/index")}>
</Button>
</View>

View File

@@ -1,7 +1,7 @@
.repair-page {
position: relative;
min-height: 100vh;
padding: 28rpx 24rpx 72rpx;
padding: 0 24rpx 72rpx;
box-sizing: border-box;
background: linear-gradient(180deg, #0b1220 0%, #121a2c 100%);
overflow: hidden;
@@ -29,7 +29,6 @@
background: radial-gradient(circle, rgba(76, 118, 214, 0.14) 0%, rgba(76, 118, 214, 0) 72%);
}
.repair-page__toolbar,
.repair-page__tabs,
.repair-page__card,
.repair-page__add-card,
@@ -38,56 +37,27 @@
z-index: 1;
}
.repair-page__toolbar {
.repair-page__header-row {
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
gap: 20rpx;
margin: 12rpx 0 24rpx;
}
.repair-page__toolbar-spacer {
width: 1rpx;
height: 1rpx;
.repair-page__header-subtitle {
flex: 1;
color: rgba(255, 255, 255, 0.58);
font-size: 24rpx;
line-height: 1.6;
}
.repair-page__history {
display: flex;
align-items: center;
gap: 12rpx;
padding: 8rpx 0 8rpx 12rpx;
}
.repair-page__clock {
position: relative;
width: 34rpx;
height: 34rpx;
border: 2rpx solid rgba(255, 255, 255, 0.8);
border-radius: 50%;
}
.repair-page__clock-hand {
position: absolute;
left: 50%;
bottom: 50%;
width: 2rpx;
background: rgba(255, 255, 255, 0.8);
border-radius: 999rpx;
transform-origin: bottom center;
}
.repair-page__clock-hand--hour {
height: 9rpx;
transform: translateX(-50%) rotate(18deg);
}
.repair-page__clock-hand--minute {
height: 12rpx;
transform: translateX(-50%) rotate(112deg);
}
.repair-page__history-text {
color: rgba(255, 255, 255, 0.76);
font-size: 22rpx;
.repair-page__header-action {
flex-shrink: 0;
color: #35e5b3;
font-size: 24rpx;
}
.repair-page__tabs {

View File

@@ -1,6 +1,8 @@
import { Button, Image, Input, Text, Textarea, View } from "@tarojs/components";
import Taro from "@tarojs/taro";
import { useMemo, useState } from "react";
import AppBar from "../../components/app-bar";
import { navigateBackWithFallback } from "../../utils/app-bar";
import {
REPAIR_DESCRIPTION_LIMIT,
REPAIR_DEVICE_TYPE_LABELS,
@@ -236,16 +238,16 @@ export default function RepairPage() {
<View className="repair-page__glow repair-page__glow--left" />
<View className="repair-page__glow repair-page__glow--right" />
<View className="repair-page__toolbar">
<View className="repair-page__toolbar-spacer" />
<View className="repair-page__history" onClick={() => showToast("历史记录功能待开放")}>
<View className="repair-page__clock">
<View className="repair-page__clock-hand repair-page__clock-hand--hour" />
<View className="repair-page__clock-hand repair-page__clock-hand--minute" />
</View>
<Text className="repair-page__history-text"></Text>
</View>
<AppBar
title="申请报修"
showBack
onBack={() => navigateBackWithFallback("/pages/mine/index")}
/>
<View className="repair-page__header-row">
<Text className="repair-page__header-subtitle"></Text>
<Text className="repair-page__header-action" onClick={() => showToast("历史记录功能待开放")}>
</Text>
</View>
<View className="repair-page__tabs">

View File

@@ -9,7 +9,7 @@
position: relative;
z-index: 1;
min-height: 100vh;
padding: calc(var(--report-top-gap, 0px) + 20rpx) 24rpx 56rpx;
padding: 0 24rpx 56rpx;
box-sizing: border-box;
}
@@ -35,51 +35,25 @@
background: radial-gradient(circle, rgba(36, 174, 255, 0.14) 0%, rgba(36, 174, 255, 0) 70%);
}
.report-header {
.report-page__meta-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
gap: 20rpx;
margin: 12rpx 0 24rpx;
}
.report-header__action {
display: flex;
align-items: center;
justify-content: center;
min-width: 68rpx;
height: 52rpx;
padding: 0 16rpx;
border-radius: 18rpx;
background: rgba(32, 42, 58, 0.82);
box-shadow: inset 0 0 0 2rpx rgba(255, 255, 255, 0.04);
}
.report-header__action--share {
min-width: 96rpx;
}
.report-header__action-text {
color: #dce7f8;
font-size: 24rpx;
}
.report-header__content {
.report-page__date-label {
flex: 1;
padding: 0 20rpx;
color: #91a3b8;
font-size: 24rpx;
line-height: 1.6;
}
.report-header__title {
display: block;
color: #f7fbff;
font-size: 34rpx;
font-weight: 600;
}
.report-header__subtitle {
display: block;
margin-top: 6rpx;
color: #8ea0b6;
font-size: 22rpx;
.report-page__share-action {
flex-shrink: 0;
color: #36e4aa;
font-size: 24rpx;
}
.report-toolbar {

View File

@@ -1,7 +1,7 @@
import { ScrollView, Text, View } from "@tarojs/components";
import Taro from "@tarojs/taro";
import type { CSSProperties } from "react";
import { useMemo, useState } from "react";
import AppBar from "../../components/app-bar";
import { ReportChartCard } from "../../components/report/chart-card";
import { ReportChipGroup } from "../../components/report/chip-group";
import { ReportDaytimePrediction } from "../../components/report/daytime-prediction";
@@ -9,9 +9,9 @@ import { ReportDistributionCard } from "../../components/report/distribution-car
import { ReportInsightList } from "../../components/report/insight-list";
import { ReportKvList } from "../../components/report/kv-list";
import { ReportMetricGrid } from "../../components/report/metric-grid";
import { ReportPageHeader } from "../../components/report/page-header";
import { ReportScoreOverview } from "../../components/report/score-overview";
import { ReportSummaryBlock } from "../../components/report/summary-block";
import { navigateBackWithFallback } from "../../utils/app-bar";
import { reportDimensions, reportOptions, reportRecords } from "./mock";
import { getSleepLevel, pickReportRecord } from "./report-utils";
import type { ReportDimension } from "./types";
@@ -81,27 +81,31 @@ export default function ReportPage() {
resetSelections(dimension, dateKey, roomKey, nextDevice);
};
const pageStyle = {
"--report-top-gap": `${(typeof Taro.getWindowInfo === "function" ? Taro.getWindowInfo().statusBarHeight : Taro.getSystemInfoSync().statusBarHeight) || 0}px`
} as CSSProperties;
return (
<ScrollView className="report-page" scrollY style={pageStyle}>
<ScrollView className="report-page" scrollY>
<View className="report-page__glow report-page__glow--one" />
<View className="report-page__glow report-page__glow--two" />
<View className="report-page__content">
<ReportPageHeader
<AppBar
title={currentRecord.babyName}
subtitle={currentRecord.dateLabel}
onBack={() => Taro.navigateBack({ delta: 1 })}
onShare={() =>
Taro.showToast({
title: "分享功能待接入",
icon: "none"
})
}
showBack
onBack={() => navigateBackWithFallback("/pages/index/index")}
/>
<View className="report-page__meta-bar">
<Text className="report-page__date-label">{currentRecord.dateLabel}</Text>
<Text
className="report-page__share-action"
onClick={() =>
Taro.showToast({
title: "分享功能待接入",
icon: "none"
})
}
>
</Text>
</View>
<View className="report-toolbar">
<ReportChipGroup options={reportDimensions} value={dimension} onChange={handleDimensionChange} />

View File

@@ -0,0 +1,38 @@
type AppBarMetricInput = {
menuButtonRect?: Partial<{
top: number;
left: number;
height: number;
}> | null;
statusBarHeight?: number;
windowWidth?: number;
};
export type AppBarMetrics = {
capsuleSafeWidth: number;
menuHeight: number;
menuTop: number;
topInset: number;
};
export function computeAppBarMetrics(input: AppBarMetricInput): AppBarMetrics {
const topInset = Math.max(input.statusBarHeight || 0, 0);
const menuHeight = input.menuButtonRect?.height && input.menuButtonRect.height > 0 ? input.menuButtonRect.height : 32;
const menuTop =
typeof input.menuButtonRect?.top === "number" ? input.menuButtonRect.top : Math.max(topInset + 6, topInset);
const capsuleSafeWidth =
typeof input.menuButtonRect?.left === "number" && input.windowWidth
? Math.max(input.windowWidth - input.menuButtonRect.left, 96)
: 96;
return {
topInset,
menuTop,
menuHeight,
capsuleSafeWidth
};
}
export function resolveBackNavigation(pageStackLength: number) {
return pageStackLength > 1 ? "navigateBack" : "redirectHome";
}

27
src/utils/app-bar.ts Normal file
View File

@@ -0,0 +1,27 @@
import Taro from "@tarojs/taro";
import { computeAppBarMetrics, resolveBackNavigation, type AppBarMetrics } from "./app-bar-metrics";
export { computeAppBarMetrics, resolveBackNavigation, type AppBarMetrics } from "./app-bar-metrics";
export function getAppBarMetrics() {
const windowInfo = typeof Taro.getWindowInfo === "function" ? Taro.getWindowInfo() : Taro.getSystemInfoSync();
const menuButtonRect =
typeof Taro.getMenuButtonBoundingClientRect === "function" ? Taro.getMenuButtonBoundingClientRect() : null;
return computeAppBarMetrics({
menuButtonRect,
statusBarHeight: windowInfo.statusBarHeight,
windowWidth: windowInfo.windowWidth
});
}
export function navigateBackWithFallback(fallbackUrl: string) {
const action = resolveBackNavigation(Taro.getCurrentPages().length);
if (action === "navigateBack") {
Taro.navigateBack({ delta: 1 });
return;
}
Taro.redirectTo({ url: fallbackUrl });
}