refactor(mp): CSS 变量主题 + 登录页改造 — UI 优化 Phase 0-2

Phase 0: 建立 design token 体系
- tokens.scss 新增 --tk-pri/--tk-pri-l/--tk-pri-d/--tk-shadow-btn/--tk-shadow-tab
- .doctor-mode 覆盖为靛蓝色系,.elder-mode 非线性放大字号
- variables.scss 新增医生端色彩 + 阴影变量

Phase 1: 组件库 + 页面全局替换
- 75 个页面 SCSS $pri → var(--tk-pri) 全量替换
- 11 个新 UI 组件(PrimaryButton/TabFilter/FormInput/ProgressRing 等)
- 8 个现有组件 SCSS 更新
- 18 个医生端页面 useElderClass → useDoctorClass
- PageHeader 匹配原型 NavBar 规格

Phase 2: 登录页重写
- Logo: 方形+ → 圆形渐变 H
- 登录方式: 纯微信 → 账号密码 + 微信一键登录
- 新增 credentialLogin API + store action
- 字号/间距严格匹配原型 mp-01-login.html
This commit is contained in:
iven
2026-05-16 21:29:13 +08:00
parent 1786f0d707
commit 95e219ad5a
124 changed files with 2306 additions and 1142 deletions

View File

@@ -5,18 +5,18 @@
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120px 40px;
padding: var(--tk-gap-2xl) var(--tk-gap-xl);
}
.empty-state-icon-wrap {
width: 120px;
height: 120px;
width: var(--tk-gap-2xl);
height: var(--tk-gap-2xl);
border-radius: 50%;
background: $surface-alt;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24px;
margin-bottom: var(--tk-gap-lg);
}
.empty-state-icon-char {
@@ -29,19 +29,19 @@
.empty-state-text {
font-size: var(--tk-font-num);
color: $tx2;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.empty-state-hint {
font-size: var(--tk-font-h2);
color: var(--tk-text-secondary);
margin-bottom: 32px;
margin-bottom: var(--tk-gap-xl);
}
.empty-state-action {
background: $pri;
background: var(--tk-pri);
border-radius: 40px;
padding: 16px 48px;
padding: var(--tk-gap-md) var(--tk-gap-2xl);
}
.empty-state-action-text {

View File

@@ -13,7 +13,7 @@
width: 64px;
height: 64px;
border-radius: 32px;
background: $pri-l;
background: var(--tk-pri-l);
display: flex;
align-items: center;
justify-content: center;
@@ -24,7 +24,7 @@
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-h1);
font-weight: 600;
color: $pri-d;
color: var(--tk-pri-d);
}
.error-title {
@@ -41,7 +41,7 @@
}
.error-retry-btn {
background: $pri;
background: var(--tk-pri);
border-radius: $r-sm;
padding: 14px 48px;
}

View File

@@ -5,25 +5,25 @@
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120px 40px;
padding: var(--tk-gap-2xl) var(--tk-gap-xl);
}
.error-state-icon {
font-size: var(--tk-font-display);
margin-bottom: 24px;
margin-bottom: var(--tk-gap-lg);
}
.error-state-text {
font-size: var(--tk-font-body-lg);
color: $tx2;
margin-bottom: 32px;
margin-bottom: var(--tk-gap-xl);
text-align: center;
}
.error-state-retry {
background: $pri;
background: var(--tk-pri);
border-radius: 40px;
padding: 16px 48px;
padding: var(--tk-gap-md) var(--tk-gap-2xl);
}
.error-state-retry-text {

View File

@@ -48,7 +48,7 @@
display: inline-block;
height: 48px;
padding: 0 32px;
background: $pri;
background: var(--tk-pri);
border-radius: $r-pill;
@include flex-center;

View File

@@ -12,7 +12,7 @@
width: 48px;
height: 48px;
border: 4px solid $bd;
border-top-color: $pri;
border-top-color: var(--tk-pri);
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: 20px;

View File

@@ -17,7 +17,7 @@
&--active {
.seg-tab__text {
color: $pri;
color: var(--tk-pri);
font-weight: bold;
}
@@ -28,7 +28,7 @@
left: 30%;
right: 30%;
height: 4px;
background: $pri;
background: var(--tk-pri);
border-radius: $r-xs;
}
}

View File

@@ -44,7 +44,7 @@
z-index: 1;
&.step-current {
background: $pri;
background: var(--tk-pri);
color: white;
}
@@ -61,7 +61,7 @@
text-align: center;
&.step-current {
color: $pri;
color: var(--tk-pri);
font-weight: bold;
}

View File

@@ -15,7 +15,7 @@
.week-arrow {
font-size: var(--tk-font-body-lg);
color: $pri;
color: var(--tk-pri);
padding: 0 16px;
}
@@ -52,7 +52,7 @@
}
.cell-today {
color: $pri;
color: var(--tk-pri);
font-weight: bold;
}
@@ -68,7 +68,7 @@
}
.cell-selected {
background: $pri;
background: var(--tk-pri);
border-radius: $r-sm;
.cell-date { color: white; }

View File

@@ -4,9 +4,10 @@
display: flex;
align-items: center;
justify-content: space-between;
height: var(--tk-touch-min);
height: 44px;
padding: 0 var(--tk-page-padding);
background: $bg;
border-bottom: 1px solid $bd-l;
z-index: 10;
&--sticky {
@@ -17,7 +18,7 @@
&__left {
display: flex;
align-items: center;
gap: 8px;
gap: var(--tk-gap-xs);
min-width: 0;
flex: 1;
}
@@ -27,19 +28,19 @@
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
min-height: var(--tk-touch-min);
height: 44px;
}
&__back-icon {
font-size: 24px;
color: $tx;
font-size: var(--tk-font-h2);
color: var(--tk-pri);
line-height: 1;
}
&__title {
font-size: var(--tk-font-h1);
font-weight: 600;
font-family: Georgia, 'Times New Roman', serif;
font-size: var(--tk-font-nav);
font-weight: 700;
color: $tx;
overflow: hidden;
text-overflow: ellipsis;
@@ -49,7 +50,7 @@
&__right {
display: flex;
align-items: center;
gap: 12px;
gap: var(--tk-gap-sm);
flex-shrink: 0;
}
}

View File

@@ -11,7 +11,7 @@
display: flex;
align-items: center;
justify-content: center;
padding: 8px 20px;
padding: var(--tk-gap-xs) var(--tk-section-gap);
border-radius: $r-sm;
background: var(--tk-card-bg);
border: 1px solid $bd;

View File

@@ -6,16 +6,16 @@
&__input-wrap {
display: flex;
align-items: center;
gap: 8px;
gap: var(--tk-gap-xs);
background: var(--tk-card-bg);
border-radius: var(--tk-card-radius);
padding: 0 16px;
height: var(--tk-touch-min);
padding: 0 var(--tk-gap-md);
height: var(--tk-input-height);
box-shadow: $shadow-sm;
}
&__icon {
font-size: 16px;
font-size: var(--tk-font-body-sm);
flex-shrink: 0;
}

View File

@@ -0,0 +1,52 @@
@import '../../../styles/variables.scss';
.alert-card {
border-radius: $r;
padding: var(--tk-gap-lg);
margin-bottom: var(--tk-gap-md);
// 渐变型 — 智能提醒
&--gradient {
background: linear-gradient(135deg, var(--tk-pri) 0%, var(--tk-pri-d) 100%);
color: $white;
}
// 左边框型 — AI 建议
&--left-border {
background: $acc-l;
border-left: 4px solid $acc;
}
// 全边框型 — 温馨提示
&--bordered {
background: $wrn-l;
border-radius: $r-sm;
}
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--tk-gap-xs);
}
&__title {
font-size: var(--tk-font-body);
font-weight: 600;
}
&--left-border &__title {
color: $acc;
}
&__subtitle {
font-size: var(--tk-font-micro);
opacity: 0.7;
}
&__body {
font-size: var(--tk-font-cap);
color: $tx2;
line-height: 1.6;
}
}

View File

@@ -0,0 +1,41 @@
import React, { ReactNode } from 'react';
import { View, Text } from '@tarojs/components';
import './index.scss';
type AlertVariant = 'gradient' | 'left-border' | 'bordered';
interface AlertCardProps {
variant?: AlertVariant;
title?: string;
subtitle?: string;
children?: ReactNode;
className?: string;
}
const AlertCard: React.FC<AlertCardProps> = ({
variant = 'left-border',
title,
subtitle,
children,
className = '',
}) => {
const cls = [
'alert-card',
`alert-card--${variant}`,
className,
].filter(Boolean).join(' ');
return (
<View className={cls}>
{title && (
<View className='alert-card__header'>
<Text className='alert-card__title'>{title}</Text>
{subtitle && <Text className='alert-card__subtitle'>{subtitle}</Text>}
</View>
)}
{children ?? <Text className='alert-card__body'>{subtitle}</Text>}
</View>
);
};
export default React.memo(AlertCard);

View File

@@ -0,0 +1,37 @@
@import '../../../styles/variables.scss';
.chat-bubble-wrap {
display: flex;
flex-direction: column;
margin-bottom: var(--tk-gap-xs);
}
.chat-bubble {
max-width: 75%;
padding: var(--tk-gap-md) var(--tk-gap-lg);
font-size: var(--tk-font-body);
line-height: 1.5;
&--other {
align-self: flex-start;
background: $card;
border-radius: $r $r $r $r-xs;
}
&--mine {
align-self: flex-end;
background: var(--tk-pri-l);
border-radius: $r $r $r-xs $r;
}
&__text {
color: $tx;
}
&__time {
font-size: var(--tk-font-micro);
color: $tx3;
margin-top: 4px;
text-align: center;
}
}

View File

@@ -0,0 +1,34 @@
import React, { ReactNode } from 'react';
import { View, Text } from '@tarojs/components';
import './index.scss';
interface ChatBubbleProps {
content: string;
isMine?: boolean;
time?: string;
className?: string;
}
const ChatBubble: React.FC<ChatBubbleProps> = ({
content,
isMine = false,
time,
className = '',
}) => {
const cls = [
'chat-bubble',
isMine ? 'chat-bubble--mine' : 'chat-bubble--other',
className,
].filter(Boolean).join(' ');
return (
<View className='chat-bubble-wrap'>
<View className={cls}>
<Text className='chat-bubble__text'>{content}</Text>
</View>
{time && <Text className='chat-bubble__time'>{time}</Text>}
</View>
);
};
export default React.memo(ChatBubble);

View File

@@ -15,9 +15,9 @@ interface ContentCardProps {
const PADDING_MAP = {
none: '0',
sm: '12px',
sm: 'var(--tk-card-padding-sm)',
md: 'var(--tk-card-padding)',
lg: '32px',
lg: 'var(--tk-card-padding-lg)',
} as const;
const ContentCard: React.FC<ContentCardProps> = ({

View File

@@ -0,0 +1,51 @@
@import '../../../styles/variables.scss';
.form-input {
&__label {
display: block;
font-size: var(--tk-font-cap);
color: $tx3;
margin-bottom: 6px;
}
&__field {
height: var(--tk-input-height);
background: $card;
border: 1.5px solid $bd;
border-radius: $r;
padding: 0 var(--tk-gap-lg);
display: flex;
align-items: center;
transition: border-color 0.2s;
}
&__control {
width: 100%;
height: 100%;
font-size: var(--tk-font-body);
color: $tx;
}
&__placeholder {
color: $tx3;
}
&--error &__field {
border-color: $dan;
}
&--disabled &__field {
opacity: 0.5;
}
&--focus &__field {
border-color: var(--tk-pri);
}
&__error {
display: block;
font-size: var(--tk-font-cap);
color: $dan;
margin-top: 4px;
}
}

View File

@@ -0,0 +1,55 @@
import React from 'react';
import { View, Text, Input } from '@tarojs/components';
import './index.scss';
interface FormInputProps {
label?: string;
placeholder?: string;
value?: string;
onInput?: (value: string) => void;
type?: 'text' | 'number' | 'idcard' | 'digit';
maxLength?: number;
disabled?: boolean;
error?: string;
className?: string;
}
const FormInput: React.FC<FormInputProps> = ({
label,
placeholder,
value,
onInput,
type = 'text',
maxLength,
disabled = false,
error,
className = '',
}) => {
const cls = [
'form-input',
error && 'form-input--error',
disabled && 'form-input--disabled',
className,
].filter(Boolean).join(' ');
return (
<View className={cls}>
{label && <Text className='form-input__label'>{label}</Text>}
<View className='form-input__field'>
<Input
className='form-input__control'
placeholder={placeholder}
placeholderClass='form-input__placeholder'
value={value}
onInput={e => onInput?.(e.detail.value)}
type={type}
maxlength={maxLength}
disabled={disabled}
/>
</View>
{error && <Text className='form-input__error'>{error}</Text>}
</View>
);
};
export default React.memo(FormInput);

View File

@@ -0,0 +1,8 @@
@import '../../../styles/variables.scss';
.gradient-header {
background: linear-gradient(135deg, var(--tk-pri) 0%, var(--tk-pri-d) 100%);
border-radius: $r;
padding: 18px;
color: $white;
}

View File

@@ -0,0 +1,21 @@
import React, { ReactNode } from 'react';
import { View } from '@tarojs/components';
import './index.scss';
interface GradientHeaderProps {
children: ReactNode;
className?: string;
}
const GradientHeader: React.FC<GradientHeaderProps> = ({
children,
className = '',
}) => {
return (
<View className={`gradient-header ${className}`}>
{children}
</View>
);
};
export default React.memo(GradientHeader);

View File

@@ -0,0 +1,27 @@
@import '../../../styles/variables.scss';
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--tk-gap-md) 0;
border-bottom: 1px solid $bd-l;
&--last {
border-bottom: none;
}
&__label {
font-size: var(--tk-font-body);
color: $tx2;
flex-shrink: 0;
}
&__value {
font-size: var(--tk-font-body-lg);
color: $tx;
text-align: right;
flex: 1;
margin-left: var(--tk-gap-md);
}
}

View File

@@ -0,0 +1,34 @@
import React, { ReactNode } from 'react';
import { View, Text } from '@tarojs/components';
import './index.scss';
interface InfoRowProps {
label: string;
value?: string;
valueNode?: ReactNode;
last?: boolean;
className?: string;
}
const InfoRow: React.FC<InfoRowProps> = ({
label,
value,
valueNode,
last = false,
className = '',
}) => {
const cls = [
'info-row',
last && 'info-row--last',
className,
].filter(Boolean).join(' ');
return (
<View className={cls}>
<Text className='info-row__label'>{label}</Text>
{valueNode ?? <Text className='info-row__value'>{value}</Text>}
</View>
);
};
export default React.memo(InfoRow);

View File

@@ -0,0 +1,67 @@
@import '../../../styles/variables.scss';
.list-item {
display: flex;
align-items: center;
background: $card;
border-radius: $r;
padding: var(--tk-gap-lg);
box-shadow: $shadow-sm;
gap: var(--tk-gap-md);
&--pressable {
&:active {
opacity: var(--tk-touch-feedback-opacity);
}
}
&--read {
opacity: 0.7;
}
&__icon {
flex-shrink: 0;
width: 44px;
height: 44px;
border-radius: 22px;
display: flex;
align-items: center;
justify-content: center;
}
&__body {
flex: 1;
min-width: 0;
}
&__title {
display: block;
font-size: var(--tk-font-body);
font-weight: 500;
color: $tx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__subtitle {
display: block;
font-size: var(--tk-font-cap);
color: $tx3;
margin-top: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__extra {
flex-shrink: 0;
}
&__arrow {
flex-shrink: 0;
font-size: var(--tk-font-body-lg);
color: $tx3;
margin-left: var(--tk-gap-2xs);
}
}

View File

@@ -0,0 +1,46 @@
import React, { ReactNode } from 'react';
import { View, Text } from '@tarojs/components';
import './index.scss';
interface ListItemProps {
title: string;
subtitle?: string;
extra?: ReactNode;
leftIcon?: ReactNode;
onPress?: () => void;
showArrow?: boolean;
unread?: boolean;
className?: string;
}
const ListItem: React.FC<ListItemProps> = ({
title,
subtitle,
extra,
leftIcon,
onPress,
showArrow = false,
unread = false,
className = '',
}) => {
const cls = [
'list-item',
onPress && 'list-item--pressable',
!unread && 'list-item--read',
className,
].filter(Boolean).join(' ');
return (
<View className={cls} onClick={onPress}>
{leftIcon && <View className='list-item__icon'>{leftIcon}</View>}
<View className='list-item__body'>
<Text className='list-item__title'>{title}</Text>
{subtitle && <Text className='list-item__subtitle'>{subtitle}</Text>}
</View>
{extra && <View className='list-item__extra'>{extra}</View>}
{showArrow && <Text className='list-item__arrow'></Text>}
</View>
);
};
export default React.memo(ListItem);

View File

@@ -35,7 +35,7 @@
&__row {
display: flex;
align-items: center;
gap: 12px;
gap: var(--tk-gap-sm);
}
&__circle {
@@ -50,7 +50,7 @@
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
gap: var(--tk-gap-xs);
}
&__line {

View File

@@ -13,9 +13,9 @@ interface PageShellProps {
const PADDING_MAP = {
none: '0',
sm: '16px',
sm: 'var(--tk-gap-md)',
md: 'var(--tk-page-padding)',
lg: '32px',
lg: 'var(--tk-gap-xl)',
} as const;
const PageShell: React.FC<PageShellProps> = ({

View File

@@ -0,0 +1,54 @@
@import '../../../styles/variables.scss';
.primary-btn {
display: flex;
align-items: center;
justify-content: center;
gap: var(--tk-gap-xs);
width: 100%;
background: var(--tk-pri);
color: $white;
font-weight: 600;
border: none;
border-radius: 14px;
box-shadow: var(--tk-shadow-btn);
transition: opacity 0.15s, transform 0.15s;
&--default {
height: var(--tk-btn-primary-h);
font-size: var(--tk-font-body-lg);
}
&--large {
height: 54px;
font-size: var(--tk-font-h2);
}
&:active:not(&--disabled):not(&--loading) {
opacity: var(--tk-touch-feedback-opacity);
transform: scale(0.98);
}
&--disabled {
opacity: 0.5;
box-shadow: none;
}
&__spinner {
width: 18px;
height: 18px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: $white;
border-radius: 50%;
animation: primary-btn-spin 0.6s linear infinite;
}
&__text {
color: $white;
font-weight: 600;
}
}
@keyframes primary-btn-spin {
to { transform: rotate(360deg); }
}

View File

@@ -0,0 +1,38 @@
import React from 'react';
import { View, Text } from '@tarojs/components';
import './index.scss';
interface PrimaryButtonProps {
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
loading?: boolean;
size?: 'default' | 'large';
className?: string;
}
const PrimaryButton: React.FC<PrimaryButtonProps> = ({
children,
onClick,
disabled = false,
loading = false,
size = 'default',
className = '',
}) => {
const cls = [
'primary-btn',
`primary-btn--${size}`,
disabled && 'primary-btn--disabled',
loading && 'primary-btn--loading',
className,
].filter(Boolean).join(' ');
return (
<View className={cls} onClick={!disabled && !loading ? onClick : undefined}>
{loading && <View className='primary-btn__spinner' />}
<Text className='primary-btn__text'>{children}</Text>
</View>
);
};
export default React.memo(PrimaryButton);

View File

@@ -0,0 +1,28 @@
@import '../../../styles/variables.scss';
.progress-ring {
position: relative;
flex-shrink: 0;
&__center {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
}
&__pct {
font-family: Georgia, 'Times New Roman', serif;
font-size: var(--tk-font-cap);
font-weight: 700;
color: var(--tk-pri);
}
&__label {
font-family: Georgia, 'Times New Roman', serif;
font-size: var(--tk-font-micro);
font-weight: 700;
color: var(--tk-pri);
}
}

View File

@@ -0,0 +1,56 @@
import React from 'react';
import { View, Text } from '@tarojs/components';
import './index.scss';
interface ProgressRingProps {
progress: number;
size?: 'sm' | 'lg';
label?: string;
className?: string;
}
const ProgressRing: React.FC<ProgressRingProps> = ({
progress,
size = 'sm',
label,
className = '',
}) => {
const px = size === 'sm' ? 64 : 80;
const r = (px / 2) - 4;
const circumference = 2 * Math.PI * r;
const offset = circumference * (1 - Math.min(progress, 1));
const cls = ['progress-ring', `progress-ring--${size}`, className].filter(Boolean).join(' ');
return (
<View className={cls} style={{ width: px, height: px }}>
<svg width={px} height={px} viewBox={`0 0 ${px} ${px}`}>
<circle
cx={px / 2} cy={px / 2} r={r}
fill='none'
stroke='var(--tk-pri-l, #E8E2DC)'
strokeWidth={4}
/>
<circle
cx={px / 2} cy={px / 2} r={r}
fill='none'
stroke='var(--tk-pri)'
strokeWidth={4}
strokeDasharray={circumference}
strokeDashoffset={offset}
strokeLinecap='round'
transform={`rotate(-90 ${px / 2} ${px / 2})`}
/>
</svg>
<View className='progress-ring__center'>
{label ? (
<Text className='progress-ring__label'>{label}</Text>
) : (
<Text className='progress-ring__pct'>{Math.round(progress * 100)}%</Text>
)}
</View>
</View>
);
};
export default React.memo(ProgressRing);

View File

@@ -0,0 +1,30 @@
@import '../../../styles/variables.scss';
.secondary-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: var(--tk-btn-primary-h);
background: transparent;
color: var(--tk-pri);
font-weight: 600;
border: 2px solid var(--tk-pri);
border-radius: 14px;
transition: opacity 0.15s, transform 0.15s;
&:active:not(&--disabled) {
opacity: var(--tk-touch-feedback-opacity);
transform: scale(0.98);
}
&--disabled {
opacity: 0.5;
}
&__text {
color: var(--tk-pri);
font-size: var(--tk-font-body-lg);
font-weight: 600;
}
}

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { View, Text } from '@tarojs/components';
import './index.scss';
interface SecondaryButtonProps {
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
className?: string;
}
const SecondaryButton: React.FC<SecondaryButtonProps> = ({
children,
onClick,
disabled = false,
className = '',
}) => {
const cls = [
'secondary-btn',
disabled && 'secondary-btn--disabled',
className,
].filter(Boolean).join(' ');
return (
<View className={cls} onClick={!disabled ? onClick : undefined}>
<Text className='secondary-btn__text'>{children}</Text>
</View>
);
};
export default React.memo(SecondaryButton);

View File

@@ -9,13 +9,13 @@
&__left {
display: flex;
align-items: center;
gap: 10px;
gap: var(--tk-gap-sm);
}
&__bar {
width: 3px;
height: 20px;
background: $pri;
background: var(--tk-pri);
border-radius: 2px;
flex-shrink: 0;
}
@@ -43,7 +43,7 @@
&__action {
font-size: var(--tk-font-body-sm);
color: $pri;
color: var(--tk-pri);
min-height: var(--tk-touch-min);
display: flex;
align-items: center;

View File

@@ -11,7 +11,7 @@
white-space: nowrap;
&--sm {
padding: 2px 8px;
font-size: 11px;
padding: 2px var(--tk-gap-xs);
font-size: var(--tk-font-micro);
}
}

View File

@@ -0,0 +1,87 @@
@import '../../../styles/variables.scss';
.tab-filter {
display: flex;
gap: var(--tk-gap-xs);
// 填充型 — 体征类型切换
&--fill {
.tab-filter__item {
flex: 1;
height: 44px;
border-radius: $r-sm;
background: $surface-alt;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
&--active {
background: var(--tk-pri);
box-shadow: var(--tk-shadow-tab);
.tab-filter__text {
color: $white;
font-weight: 600;
}
}
}
}
// Pill型 — 文章分类
&--pill {
flex-wrap: wrap;
gap: var(--tk-gap-xs);
.tab-filter__item {
height: 32px;
padding: 0 var(--tk-gap-lg);
border-radius: $r-pill;
background: $surface-alt;
display: flex;
align-items: center;
justify-content: center;
&--active {
background: var(--tk-pri);
.tab-filter__text {
color: $white;
font-weight: 600;
}
}
}
}
// 段控型 — 消息页咨询/通知
&--segment {
background: $surface-alt;
border-radius: $r-xs;
padding: 3px;
.tab-filter__item {
flex: 1;
height: 40px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
&--active {
background: $card;
box-shadow: $shadow-sm;
.tab-filter__text {
color: $tx;
font-weight: 600;
}
}
}
}
&__text {
font-size: var(--tk-font-cap);
color: $tx2;
}
}

View File

@@ -0,0 +1,43 @@
import React from 'react';
import { View, Text } from '@tarojs/components';
import './index.scss';
type TabVariant = 'fill' | 'pill' | 'segment';
interface TabFilterProps {
tabs: string[];
activeIndex: number;
onChange: (index: number) => void;
variant?: TabVariant;
className?: string;
}
const TabFilter: React.FC<TabFilterProps> = ({
tabs,
activeIndex,
onChange,
variant = 'fill',
className = '',
}) => {
const cls = [
'tab-filter',
`tab-filter--${variant}`,
className,
].filter(Boolean).join(' ');
return (
<View className={cls}>
{tabs.map((tab, i) => (
<View
key={i}
className={`tab-filter__item ${i === activeIndex ? 'tab-filter__item--active' : ''}`}
onClick={() => onChange(i)}
>
<Text className='tab-filter__text'>{tab}</Text>
</View>
))}
</View>
);
};
export default React.memo(TabFilter);

View File

@@ -0,0 +1,41 @@
@import '../../../styles/variables.scss';
.vital-card {
background: $card;
border-radius: $r;
padding: 14px var(--tk-gap-lg);
box-shadow: $shadow-sm;
&--pressable {
&:active {
opacity: var(--tk-touch-feedback-opacity);
}
}
&__label {
display: block;
font-size: var(--tk-font-cap);
color: $tx2;
margin-bottom: 6px;
}
&__row {
display: flex;
align-items: baseline;
margin-bottom: 6px;
}
&__value {
font-family: Georgia, 'Times New Roman', serif;
font-size: var(--tk-font-num);
font-weight: 700;
color: $tx;
line-height: 1;
}
&__unit {
font-size: var(--tk-font-micro);
color: $tx3;
margin-left: 3px;
}
}

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { View, Text } from '@tarojs/components';
import StatusTag from '../StatusTag';
import './index.scss';
interface VitalCardProps {
label: string;
value: string;
unit?: string;
status?: string;
onPress?: () => void;
className?: string;
}
const VitalCard: React.FC<VitalCardProps> = ({
label,
value,
unit,
status,
onPress,
className = '',
}) => {
const cls = [
'vital-card',
onPress && 'vital-card--pressable',
className,
].filter(Boolean).join(' ');
return (
<View className={cls} onClick={onPress}>
<Text className='vital-card__label'>{label}</Text>
<View className='vital-card__row'>
<Text className='vital-card__value'>{value}</Text>
{unit && <Text className='vital-card__unit'>{unit}</Text>}
</View>
{status && <StatusTag status={status} size='sm' />}
</View>
);
};
export default React.memo(VitalCard);

View File

@@ -0,0 +1,6 @@
import { useUIStore } from '../stores/ui';
export function useDoctorClass(): string {
const mode = useUIStore((s) => s.mode);
return ['doctor-mode', mode === 'elder' ? 'elder-mode' : ''].filter(Boolean).join(' ');
}

View File

@@ -3,7 +3,7 @@
.detail-type {
@include section-title;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.detail-meta {
@@ -21,30 +21,30 @@
h1, h2, h3 {
font-weight: bold;
color: $tx;
margin: 24px 0 12px;
margin: var(--tk-gap-lg) 0 var(--tk-gap-sm);
}
p {
font-size: var(--tk-font-body-lg);
color: $tx;
line-height: 1.8;
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
ul {
padding-left: 32px;
margin-bottom: 16px;
padding-left: var(--tk-gap-xl);
margin-bottom: var(--tk-gap-md);
}
li {
font-size: var(--tk-font-body-lg);
color: $tx;
line-height: 1.8;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
strong {
color: $pri-d;
color: var(--tk-pri-d);
}
}
@@ -57,32 +57,32 @@
.empty-text {
display: block;
text-align: center;
padding: 120px 0;
padding: var(--tk-gap-2xl) 0;
color: var(--tk-text-secondary);
font-size: var(--tk-font-body-lg);
}
.auto-badge {
margin-top: 16px;
margin-top: var(--tk-gap-md);
display: inline-block;
}
.auto-badge-text {
display: inline-block;
padding: 4px 16px;
padding: var(--tk-gap-2xs) var(--tk-gap-md);
border-radius: $r-xs;
font-size: var(--tk-font-body);
font-weight: 500;
background: $pri-l;
color: $pri;
background: var(--tk-pri-l);
color: var(--tk-pri);
}
.trend-tip-card {
background: $wrn-l;
border: 1px solid $wrn;
border-radius: $r;
padding: 20px 24px;
margin-bottom: 20px;
padding: var(--tk-section-gap) var(--tk-gap-lg);
margin-bottom: var(--tk-section-gap);
}
.trend-tip-text {

View File

@@ -14,8 +14,8 @@
.report-card {
background: $card;
border-radius: $r;
padding: 28px;
margin-bottom: 20px;
padding: var(--tk-card-padding-lg);
margin-bottom: var(--tk-section-gap);
box-shadow: $shadow-sm;
}
@@ -23,7 +23,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.card-type {
@@ -41,7 +41,7 @@
}
.status-streaming {
@include tag($pri-l, $pri);
@include tag(var(--tk-pri-l), var(--tk-pri));
}
.status-failed {
@@ -72,6 +72,6 @@
text-align: center;
font-size: var(--tk-font-h2);
color: var(--tk-text-secondary);
padding: 24px 0;
padding: var(--tk-gap-lg) 0;
display: block;
}

View File

@@ -9,7 +9,7 @@
/* 步骤内容 */
.step-content {
padding: 32px 24px;
padding: var(--tk-gap-xl) var(--tk-gap-lg);
}
.step-title {
@@ -20,24 +20,24 @@
.dept-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
gap: var(--tk-gap-md);
}
.dept-card {
background: $card;
border-radius: $r;
padding: 28px 12px;
padding: var(--tk-card-padding-lg) var(--tk-gap-sm);
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
gap: var(--tk-gap-sm);
border: 2px solid transparent;
transition: border-color 0.2s;
box-shadow: $shadow-sm;
&.dept-selected {
border-color: $pri;
background: $pri-l;
border-color: var(--tk-pri);
background: var(--tk-pri-l);
}
}
@@ -45,11 +45,11 @@
width: 64px;
height: 64px;
border-radius: $r;
background: $pri-l;
background: var(--tk-pri-l);
@include flex-center;
.dept-selected & {
background: $pri;
background: var(--tk-pri);
}
}
@@ -57,7 +57,7 @@
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-num);
font-weight: bold;
color: $pri;
color: var(--tk-pri);
.dept-selected & {
color: $white;
@@ -72,7 +72,7 @@
/* 时段 */
.slot-section {
margin-top: 32px;
margin-top: var(--tk-gap-xl);
}
.slot-section-title {
@@ -80,20 +80,20 @@
font-size: var(--tk-font-body-lg);
font-weight: bold;
color: $tx;
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
display: block;
}
.slot-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
gap: var(--tk-gap-sm);
}
.slot-card {
background: $card;
border-radius: $r-sm;
padding: 20px 24px;
padding: var(--tk-section-gap) var(--tk-gap-lg);
border: 2px solid transparent;
transition: all 0.2s;
box-shadow: $shadow-sm;
@@ -107,8 +107,8 @@
pointer-events: none;
}
&.slot-selected {
border-color: $pri;
background: $pri-l;
border-color: var(--tk-pri);
background: var(--tk-pri-l);
}
}
@@ -124,7 +124,7 @@
font-size: var(--tk-font-body);
color: $tx3;
display: block;
margin-top: 6px;
margin-top: var(--tk-gap-2xs);
}
.slot-few .slot-count { color: $wrn; }
@@ -132,20 +132,20 @@
/* 确认卡片 (step 3 医生信息) */
.confirm-card {
margin-bottom: 24px;
margin-bottom: var(--tk-gap-lg);
}
.confirm-row {
display: flex;
align-items: center;
gap: 16px;
gap: var(--tk-gap-md);
}
.confirm-icon-wrap {
width: 56px;
height: 56px;
border-radius: $r-sm;
background: $pri-l;
background: var(--tk-pri-l);
@include flex-center;
flex-shrink: 0;
}
@@ -154,7 +154,7 @@
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-h2);
font-weight: bold;
color: $pri;
color: var(--tk-pri);
}
.confirm-info {
@@ -176,37 +176,37 @@
}
.confirm-dept-tag {
@include tag($pri-l, $pri);
@include tag(var(--tk-pri-l), var(--tk-pri));
flex-shrink: 0;
}
.confirm-dept-text {
font-size: var(--tk-font-body);
font-weight: 500;
color: $pri;
color: var(--tk-pri);
}
/* 医生列表 */
.doctor-list {
display: flex;
flex-direction: column;
gap: 16px;
gap: var(--tk-gap-md);
}
.doctor-card {
background: $card;
border-radius: $r;
padding: 24px 28px;
padding: var(--tk-gap-lg) var(--tk-card-padding-lg);
display: flex;
align-items: center;
gap: 20px;
gap: var(--tk-section-gap);
box-shadow: $shadow-sm;
border: 2px solid transparent;
transition: border-color 0.2s;
&.doctor-selected {
border-color: $pri;
background: $pri-l;
border-color: var(--tk-pri);
background: var(--tk-pri-l);
}
}
@@ -214,7 +214,7 @@
width: 80px;
height: 80px;
border-radius: $r;
background: $pri-l;
background: var(--tk-pri-l);
@include flex-center;
flex-shrink: 0;
}
@@ -222,7 +222,7 @@
.doctor-avatar-text {
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-num);
color: $pri;
color: var(--tk-pri);
font-weight: bold;
}
@@ -230,7 +230,7 @@
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
gap: var(--tk-gap-2xs);
}
.doctor-name {
@@ -246,14 +246,14 @@
.doctor-specialty {
font-size: var(--tk-font-body);
color: $pri;
color: var(--tk-pri);
}
.doctor-check {
width: 48px;
height: 48px;
border-radius: $r-pill;
background: $pri;
background: var(--tk-pri);
@include flex-center;
flex-shrink: 0;
}
@@ -266,20 +266,20 @@
/* 表单 */
.form-group {
margin-top: 32px;
margin-top: var(--tk-gap-xl);
}
.form-label {
font-size: var(--tk-font-h1);
color: $tx2;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
display: block;
}
.form-input {
background: $card;
border-radius: $r-sm;
padding: 24px 28px;
padding: var(--tk-gap-lg) var(--tk-card-padding-lg);
font-size: var(--tk-font-body-lg);
color: $tx;
width: 100%;
@@ -305,8 +305,8 @@
left: 0;
right: 0;
display: flex;
gap: 16px;
padding: 20px 24px;
gap: var(--tk-gap-md);
padding: var(--tk-section-gap) var(--tk-gap-lg);
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
background: $card;
@@ -315,7 +315,7 @@
.btn {
flex: 1;
padding: 24px 0;
padding: var(--tk-gap-lg) 0;
border-radius: $r-sm;
text-align: center;
transition: opacity 0.2s;
@@ -327,7 +327,7 @@
.btn-next,
.btn-submit {
background: $pri;
background: var(--tk-pri);
}
.btn-disabled {

View File

@@ -6,14 +6,14 @@
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
margin: 20px 24px 24px;
gap: var(--tk-gap-sm);
margin: var(--tk-section-gap) var(--tk-gap-lg) var(--tk-gap-lg);
}
.status-tag {
@include tag($bd-l, $tx3);
margin-bottom: 8px;
padding: 8px 32px;
margin-bottom: var(--tk-gap-xs);
padding: var(--tk-gap-xs) var(--tk-gap-xl);
border-radius: $r-pill;
&.tag-pending {
@@ -32,8 +32,8 @@
}
&.tag-completed {
background: $pri-l;
.status-tag-text { color: $pri; }
background: var(--tk-pri-l);
.status-tag-text { color: var(--tk-pri); }
}
}
@@ -56,12 +56,12 @@
/* 详情信息 */
.info-section-wrap {
margin: 0 24px 24px;
margin: 0 var(--tk-gap-lg) var(--tk-gap-lg);
}
.section-title {
@include section-title;
margin-bottom: 24px;
margin-bottom: var(--tk-gap-lg);
}
.info-item {
@@ -85,8 +85,8 @@
.info-icon-serif {
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-body);
color: $pri;
background: $pri-l;
color: var(--tk-pri);
background: var(--tk-pri-l);
width: 36px;
height: 36px;
border-radius: $r-sm;
@@ -126,7 +126,7 @@
/* 温馨提示 */
.tips-card-wrap {
margin: 0 24px 24px;
margin: 0 var(--tk-gap-lg) var(--tk-gap-lg);
background: $wrn-l;
}
@@ -135,7 +135,7 @@
font-size: var(--tk-font-body-lg);
font-weight: bold;
color: $wrn;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
display: block;
}
@@ -151,7 +151,7 @@
bottom: 0;
left: 0;
right: 0;
padding: 20px 24px;
padding: var(--tk-section-gap) var(--tk-gap-lg);
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
background: $card;
@@ -161,7 +161,7 @@
.cancel-btn {
background: $dan-l;
border-radius: $r-sm;
padding: 24px 0;
padding: var(--tk-gap-lg) 0;
text-align: center;
transition: opacity 0.2s;
}

View File

@@ -9,13 +9,13 @@
/* 页头 */
.page-header {
background: $card;
padding: 48px 32px 36px;
padding: var(--tk-gap-2xl) var(--tk-gap-xl) 36px;
box-shadow: $shadow-sm;
}
.page-title {
@include section-title;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
font-size: var(--tk-font-num-lg);
}
@@ -28,15 +28,15 @@
/* 预约列表 */
.appointment-list {
padding: 0 24px;
margin-top: 16px;
padding: 0 var(--tk-page-padding);
margin-top: var(--tk-gap-md);
}
.appointment-card {
background: $card;
border-radius: $r;
padding: 28px;
margin-bottom: 20px;
padding: var(--tk-card-padding-lg);
margin-bottom: var(--tk-section-gap);
box-shadow: $shadow-sm;
}
@@ -44,13 +44,13 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
margin-bottom: var(--tk-section-gap);
}
.doctor-section {
display: flex;
align-items: center;
gap: 16px;
gap: var(--tk-gap-md);
flex: 1;
min-width: 0;
}
@@ -59,7 +59,7 @@
width: 72px;
height: 72px;
border-radius: $r;
background: $pri-l;
background: var(--tk-pri-l);
@include flex-center;
flex-shrink: 0;
}
@@ -68,13 +68,13 @@
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-num);
font-weight: bold;
color: $pri;
color: var(--tk-pri);
}
.doctor-info {
display: flex;
flex-direction: column;
gap: 6px;
gap: var(--tk-gap-2xs);
min-width: 0;
}
@@ -88,13 +88,13 @@
}
.dept-tag {
@include tag($pri-l, $pri);
@include tag(var(--tk-pri-l), var(--tk-pri));
}
.dept-tag-text {
font-size: var(--tk-font-body);
font-weight: 500;
color: $pri;
color: var(--tk-pri);
}
.status-tag {
@@ -117,8 +117,8 @@
}
&.tag-completed {
background: $pri-l;
.status-tag-text { color: $pri; }
background: var(--tk-pri-l);
.status-tag-text { color: var(--tk-pri); }
}
}
@@ -130,7 +130,7 @@
.card-divider {
height: 1px;
background: $bd-l;
margin-bottom: 20px;
margin-bottom: var(--tk-section-gap);
}
.card-bottom {
@@ -142,7 +142,7 @@
.info-row {
display: flex;
align-items: center;
gap: 12px;
gap: var(--tk-gap-sm);
}
.info-icon-wrap {
@@ -177,8 +177,8 @@
bottom: 60px;
left: 50%;
transform: translateX(-50%);
background: $pri;
padding: 24px 72px;
background: var(--tk-pri);
padding: var(--tk-gap-lg) 72px;
border-radius: $r-pill;
box-shadow: 0 8px 24px rgba($pri, 0.3);
z-index: 100;

View File

@@ -5,7 +5,7 @@
.article-header {
background: $card;
padding: 32px;
padding: var(--tk-gap-xl);
margin-bottom: 2px;
}
@@ -15,21 +15,21 @@
color: $tx;
display: block;
line-height: 1.4;
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
.article-meta {
display: flex;
align-items: center;
gap: 16px;
gap: var(--tk-gap-md);
flex-wrap: wrap;
}
.article-category {
font-size: var(--tk-font-body);
color: $pri;
background: $pri-l;
padding: 4px 12px;
color: var(--tk-pri);
background: var(--tk-pri-l);
padding: var(--tk-gap-2xs) var(--tk-gap-sm);
border-radius: $r-sm;
}
@@ -45,7 +45,7 @@
.article-summary {
background: $card;
padding: 24px 32px;
padding: var(--tk-gap-lg) var(--tk-gap-xl);
margin-bottom: 2px;
}
@@ -57,26 +57,26 @@
.article-content {
background: $card;
padding: 32px;
padding: var(--tk-gap-xl);
// RichText 内部样式优化
h1, h2, h3 {
font-weight: bold;
color: $tx;
margin: 24px 0 12px;
margin: var(--tk-gap-lg) 0 var(--tk-gap-sm);
}
p {
font-size: var(--tk-font-body-lg);
color: $tx;
line-height: 1.8;
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
img {
max-width: 100%;
border-radius: $r-sm;
margin: 12px 0;
margin: var(--tk-gap-sm) 0;
}
}
@@ -85,7 +85,7 @@
display: flex;
justify-content: center;
align-items: center;
padding: 120px 0;
padding: var(--tk-gap-2xl) 0;
}
.loading-text,

View File

@@ -8,13 +8,13 @@
/* ─── 分类筛选滚动条(页面特有) ─── */
.article-categories {
white-space: nowrap;
margin-bottom: 24px;
margin-bottom: var(--tk-gap-lg);
}
.article-cat-tab {
display: inline-block;
padding: 12px 28px;
margin-right: 12px;
padding: var(--tk-gap-sm) var(--tk-card-padding-lg);
margin-right: var(--tk-gap-sm);
font-size: var(--tk-font-h1);
color: $tx2;
background: $card;
@@ -22,9 +22,9 @@
border: 2px solid transparent;
&--active {
color: $pri;
background: $pri-l;
border-color: $pri;
color: var(--tk-pri);
background: var(--tk-pri-l);
border-color: var(--tk-pri);
font-weight: bold;
}
}
@@ -33,7 +33,7 @@
.article-list {
display: flex;
flex-direction: column;
gap: 20px;
gap: var(--tk-section-gap);
}
/* ─── 文章卡片内容ContentCard 已接管外层卡片样式) ─── */
@@ -41,7 +41,7 @@
flex: 1;
display: flex;
flex-direction: column;
margin-right: 20px;
margin-right: var(--tk-section-gap);
}
.article-card-title {
@@ -49,7 +49,7 @@
font-weight: bold;
color: $tx;
line-height: 1.4;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
@@ -62,7 +62,7 @@
color: $tx2;
line-height: 1.4;
display: block;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -71,14 +71,14 @@
.article-card-meta {
display: flex;
align-items: center;
gap: 12px;
gap: var(--tk-gap-sm);
}
.article-card-tag {
font-size: var(--tk-font-body);
color: $pri;
background: $pri-l;
padding: 2px 12px;
color: var(--tk-pri);
background: var(--tk-pri-l);
padding: 2px var(--tk-gap-sm);
border-radius: $r-sm;
}

View File

@@ -13,20 +13,20 @@
font-size: var(--tk-font-cap);
color: var(--tk-text-secondary);
display: block;
margin-bottom: 20px;
margin-bottom: var(--tk-section-gap);
}
/* ─── 发起咨询按钮(页面特有) ─── */
.consultation-create-btn {
height: 48px;
border-radius: $r;
background: $pri;
background: var(--tk-pri);
@include flex-center;
box-shadow: 0 2px 8px rgba(196, 98, 58, 0.25);
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba($pri, 0.25);
margin-bottom: var(--tk-section-gap);
&:active {
opacity: 0.85;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -40,7 +40,7 @@
.session-list {
display: flex;
flex-direction: column;
gap: 8px;
gap: var(--tk-gap-xs);
}
/* ─── 已关闭会话半透明ContentCard 已接管卡片外层) ─── */
@@ -52,14 +52,14 @@
.session-inner {
display: flex;
align-items: center;
gap: 12px;
gap: var(--tk-gap-sm);
}
.session-avatar {
width: 36px;
height: 36px;
border-radius: $r-lg;
background: $pri-l;
background: var(--tk-pri-l);
@include flex-center;
flex-shrink: 0;
}
@@ -68,7 +68,7 @@
@include serif-number;
font-size: var(--tk-font-body-sm);
font-weight: 700;
color: $pri;
color: var(--tk-pri);
}
.session-body {
@@ -80,7 +80,7 @@
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
margin-bottom: var(--tk-gap-2xs);
}
.session-subject {
@@ -91,7 +91,7 @@
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
margin-right: 8px;
margin-right: var(--tk-gap-xs);
}
.session-time {
@@ -103,7 +103,7 @@
.session-meta {
display: flex;
align-items: center;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
}
.session-message-row {
@@ -119,7 +119,7 @@
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
margin-right: 8px;
margin-right: var(--tk-gap-xs);
}
/* ─── 未读角标(页面特有) ─── */

View File

@@ -2,12 +2,12 @@
@import '../../styles/mixins.scss';
.health-page {
padding-bottom: calc(100px + env(safe-area-inset-bottom));
padding-bottom: calc(var(--tk-tabbar-space) + env(safe-area-inset-bottom));
}
/* ─── 页头 ─── */
.health-header {
margin-bottom: 20px;
margin-bottom: var(--tk-section-gap);
}
.health-title {
@@ -19,18 +19,18 @@
/* ─── 录入区 ─── */
.input-section {
margin-bottom: 20px;
margin-bottom: var(--tk-section-gap);
}
.input-group {
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.input-label {
font-size: var(--tk-font-cap);
color: var(--tk-text-secondary);
display: block;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
}
.input-field {
@@ -38,7 +38,7 @@
background: $bg;
border: 2px solid $bd;
border-radius: $r-sm;
padding: 0 16px;
padding: 0 var(--tk-gap-md);
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-body-lg);
font-weight: 600;
@@ -51,19 +51,19 @@
font-size: var(--tk-font-cap);
color: var(--tk-text-secondary);
display: block;
margin-top: 8px;
margin-bottom: 4px;
margin-top: var(--tk-gap-xs);
margin-bottom: var(--tk-gap-2xs);
}
.input-label--secondary {
margin-top: 20px;
margin-top: var(--tk-section-gap);
}
/* ─── 血糖时段选择 ─── */
.period-group {
display: flex;
gap: 8px;
margin-top: 12px;
gap: var(--tk-gap-xs);
margin-top: var(--tk-gap-sm);
}
.period-btn {
@@ -74,7 +74,7 @@
@include flex-center;
&.period-active {
background: $pri;
background: var(--tk-pri);
.period-btn-text {
color: $white;
@@ -82,7 +82,7 @@
}
&:active {
opacity: 0.85;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -97,13 +97,13 @@
width: 100%;
height: 52px;
border-radius: $r-sm;
background: $pri;
background: var(--tk-pri);
@include flex-center;
margin-top: 20px;
box-shadow: 0 2px 8px rgba(196, 98, 58, 0.25);
margin-top: var(--tk-section-gap);
box-shadow: 0 2px 8px rgba($pri, 0.25);
&:active {
opacity: 0.85;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -115,7 +115,7 @@
/* ─── 趋势图 ─── */
.trend-section {
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.section-title {
@@ -132,7 +132,7 @@
}
.trend-chart {
padding: 16px;
padding: var(--tk-gap-md);
}
.trend-bars {
@@ -141,7 +141,7 @@
height: 120px;
background: $bg;
border-radius: $r-sm;
padding: 12px 8px;
padding: var(--tk-gap-sm) var(--tk-gap-xs);
gap: 0;
position: relative;
}
@@ -180,7 +180,7 @@
opacity: 0.8;
&.trend-bar-normal {
background: $pri;
background: var(--tk-pri);
}
&.trend-bar-warn {
@@ -191,21 +191,21 @@
.trend-bar-label {
font-size: var(--tk-font-micro);
color: var(--tk-text-secondary);
margin-top: 6px;
margin-top: var(--tk-gap-2xs);
}
/* ─── BLE 设备卡片 ─── */
.device-section {
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.device-card {
display: flex;
align-items: center;
gap: 12px;
gap: var(--tk-gap-sm);
&:active {
opacity: 0.85;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -213,7 +213,7 @@
width: 48px;
height: 48px;
border-radius: $r-sm;
background: $pri-l;
background: var(--tk-pri-l);
@include flex-center;
flex-shrink: 0;
}
@@ -249,7 +249,7 @@
/* ─── 健康资讯入口 ─── */
.article-entry {
&:active {
opacity: 0.85;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -263,8 +263,8 @@
.ai-suggestion-card {
background: $acc-l;
border-radius: $r;
padding: 16px;
margin-bottom: 20px;
padding: var(--tk-gap-md);
margin-bottom: var(--tk-section-gap);
box-shadow: none;
border-left: 4px solid $acc;
}
@@ -273,7 +273,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.ai-card-title {
@@ -291,8 +291,8 @@
.ai-suggestion-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 0;
gap: var(--tk-gap-xs);
padding: var(--tk-gap-2xs) 0;
}
.ai-risk-dot {

View File

@@ -6,7 +6,7 @@
═══════════════════════════════════════ */
.home-page {
padding-bottom: calc(100px + env(safe-area-inset-bottom));
padding-bottom: calc(var(--tk-tabbar-space) + env(safe-area-inset-bottom));
}
/* ─── 问候区 ─── */
@@ -27,7 +27,7 @@
font-weight: 700;
color: $tx;
display: block;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
}
.greeting-date {
@@ -40,18 +40,18 @@
width: 48px;
height: 48px;
border-radius: $r-pill;
background: $pri-l;
background: var(--tk-pri-l);
@include flex-center;
flex-shrink: 0;
&:active {
opacity: 0.7;
opacity: var(--tk-touch-feedback-opacity);
}
}
.greeting-bell-icon {
font-size: var(--tk-font-body-sm);
color: $pri-d;
color: var(--tk-pri-d);
}
.greeting-bell-dot {
@@ -66,13 +66,13 @@
/* ─── 今日体征进度 ─── */
.checkin-card {
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
display: flex;
align-items: center;
gap: 16px;
gap: var(--tk-gap-md);
&:active {
opacity: 0.9;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -91,18 +91,18 @@
font-weight: 600;
color: $tx;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.checkin-capsules {
display: flex;
flex-wrap: wrap;
gap: 6px;
gap: var(--tk-gap-2xs);
}
.capsule {
font-size: var(--tk-font-micro);
padding: 3px 8px;
padding: 3px var(--tk-gap-xs);
border-radius: $r-pill;
font-weight: 500;
@@ -117,9 +117,9 @@
}
}
/* ─── 今日体征 2x2 ─── */
/* ─── 今日体征 2x2(原型 padding:14px 16px, gap:10─── */
.vitals-section {
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
.section-title {
@@ -134,7 +134,7 @@
.vital-card {
&:active {
opacity: 0.7;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -142,13 +142,13 @@
font-size: var(--tk-font-cap);
color: $tx2;
display: block;
margin-bottom: 6px;
margin-bottom: var(--tk-gap-2xs);
}
.vital-value-row {
display: flex;
align-items: baseline;
margin-bottom: 6px;
margin-bottom: var(--tk-gap-2xs);
}
.vital-value {
@@ -173,10 +173,10 @@
.vital-tag {
font-size: var(--tk-font-micro);
font-weight: 500;
padding: 2px 8px;
padding: 2px var(--tk-gap-xs);
border-radius: $r-pill;
display: inline-block;
margin-top: 4px;
margin-top: var(--tk-gap-2xs);
&.tag-ok {
background: $acc-l;
@@ -196,10 +196,10 @@
/* ─── 智能提醒卡片 ─── */
.reminder-card {
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
border-radius: $r;
padding: 18px;
margin-bottom: 16px;
background: linear-gradient(135deg, var(--tk-pri) 0%, var(--tk-pri-d) 100%);
border-radius: var(--tk-card-radius);
padding: var(--tk-gap-md);
margin-bottom: var(--tk-gap-md);
color: $white;
}
@@ -207,7 +207,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
margin-bottom: var(--tk-gap-sm);
}
.reminder-title {
@@ -225,11 +225,11 @@
.reminder-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 0;
gap: var(--tk-gap-xs);
padding: var(--tk-gap-xs) 0;
&:active {
opacity: 0.8;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -239,7 +239,7 @@
.reminder-tag {
font-size: var(--tk-font-micro);
padding: 2px 6px;
padding: 2px var(--tk-gap-2xs);
border-radius: $r-xs;
background: rgba(255, 255, 255, 0.2);
font-weight: 500;
@@ -262,34 +262,34 @@
flex-shrink: 0;
}
/* ─── 快捷操作 ─── */
/* ─── 快捷操作(原型 gap:10, height:52, borderRadius:14─── */
.action-section {
display: flex;
gap: 10px;
margin-top: 8px;
margin-top: var(--tk-gap-xs);
}
.action-btn {
flex: 1;
height: 52px;
border-radius: $r-sm;
height: var(--tk-btn-primary-h);
border-radius: 14px;
@include flex-center;
&:active {
opacity: 0.85;
opacity: var(--tk-touch-feedback-opacity);
}
}
.action-primary {
background: $pri;
background: var(--tk-pri);
color: $white;
box-shadow: 0 2px 8px rgba(196, 98, 58, 0.25);
box-shadow: var(--tk-shadow-tab);
}
.action-outline {
background: transparent;
color: $pri;
border: 2px solid $pri;
color: var(--tk-pri);
border: 2px solid var(--tk-pri);
}
.action-btn-text {
@@ -302,13 +302,13 @@
═══════════════════════════════════════ */
.guest-page {
padding-bottom: calc(120px + env(safe-area-inset-bottom));
padding-bottom: calc(var(--tk-tabbar-space) + env(safe-area-inset-bottom));
}
/* ─── 轮播图 ─── */
.guest-swiper {
width: 100%;
height: 360px;
height: 400px;
}
.guest-slide {
@@ -323,7 +323,7 @@
inset: 0;
&--1 {
background: linear-gradient(135deg, $pri-d 0%, $pri 60%, $pri-l 100%);
background: linear-gradient(135deg, var(--tk-pri-d) 0%, var(--tk-pri) 60%, var(--tk-pri-l) 100%);
}
&--2 {
background: linear-gradient(135deg, $acc 0%, $acc-d 60%, $acc-l 100%);
@@ -340,7 +340,7 @@
display: flex;
flex-direction: column;
justify-content: center;
padding: 40px 32px;
padding: var(--tk-gap-2xl) var(--tk-gap-xl);
}
.guest-slide-title {
@@ -349,7 +349,7 @@
font-weight: 700;
color: $white;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.guest-slide-desc {
@@ -360,7 +360,7 @@
/* ─── 健康资讯 ─── */
.guest-section {
padding: 24px 24px 0;
padding: var(--tk-gap-lg) var(--tk-gap-lg) 0;
}
.guest-section-title {
@@ -369,13 +369,13 @@
font-weight: bold;
color: $tx;
display: block;
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
.guest-articles {
display: flex;
flex-direction: column;
gap: 12px;
gap: var(--tk-gap-sm);
}
.guest-article-card {
@@ -383,7 +383,7 @@
display: flex;
&:active {
opacity: 0.85;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -394,7 +394,7 @@
}
.guest-article-body {
padding: 12px 14px;
padding: var(--tk-gap-sm);
flex: 1;
min-width: 0;
}
@@ -404,7 +404,7 @@
font-weight: 600;
color: $tx;
display: block;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -420,7 +420,7 @@
}
.guest-empty {
padding: 40px 0;
padding: var(--tk-gap-2xl) 0;
text-align: center;
}
@@ -431,10 +431,10 @@
/* ─── 底部登录引导 ─── */
.guest-login-prompt {
margin: 24px 24px 0;
margin: var(--tk-gap-lg) var(--tk-gap-lg) 0;
display: flex;
align-items: center;
gap: 16px;
gap: var(--tk-gap-md);
}
.guest-login-text {
@@ -444,15 +444,15 @@
}
.guest-login-btn {
height: 56px;
padding: 0 28px;
background: $pri;
height: var(--tk-input-height);
padding: 0 var(--tk-card-padding-lg);
background: var(--tk-pri);
border-radius: $r-pill;
@include flex-center;
flex-shrink: 0;
&:active {
opacity: 0.85;
opacity: var(--tk-touch-feedback-opacity);
}
}

View File

@@ -8,7 +8,7 @@ import { usePageData } from '@/hooks/usePageData';
import { useThrottledDidShow } from '@/hooks/useThrottledDidShow';
import { api } from '@/services/request';
import type { Article } from '@/services/article';
import ProgressRing from '../../components/ProgressRing';
import ProgressRing from '@/components/ui/ProgressRing';
import Loading from '../../components/Loading';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
@@ -202,7 +202,7 @@ function HomeDashboard({ modeClass }: { modeClass: string }) {
<ContentCard variant="elevated" onPress={() => Taro.switchTab({ url: '/pages/health/index' })}>
<View className='checkin-left'>
<ProgressRing percent={progressPercent} />
<ProgressRing progress={progressPercent / 100} />
</View>
<View className='checkin-right'>
<Text className='checkin-title'>

View File

@@ -1,12 +1,15 @@
@import '../../styles/variables.scss';
@import '../../styles/mixins.scss';
// 登录页使用原型精确数值,不走 design token
// 原型参考docs/design/mp-01-login.html
.login-page {
// PageShell 接管 min-height, background, scroll
min-height: 100vh;
background: $bg;
display: flex;
flex-direction: column;
align-items: center;
padding: 100px 40px 60px;
padding: 80px 28px 40px;
}
/* ─── 品牌区 ─── */
@@ -14,70 +17,155 @@
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 48px;
margin-bottom: 56px;
}
.login-logo {
width: 96px;
height: 96px;
border-radius: $r-lg;
background: $pri;
width: 80px;
height: 80px;
border-radius: 40px;
background: linear-gradient(135deg, var(--tk-pri-l) 0%, var(--tk-pri) 100%);
@include flex-center;
margin-bottom: 24px;
box-shadow: 0 8px 24px rgba($pri, 0.3);
margin-bottom: 20px;
box-shadow: 0 4px 16px rgba($pri, 0.25);
}
.login-logo-mark {
.login-logo-letter {
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-hero);
font-size: 36px;
font-weight: 700;
color: $white;
font-weight: bold;
line-height: 1;
}
.login-title {
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-num);
font-size: 28px;
font-weight: 700;
color: $tx;
font-weight: bold;
margin-bottom: 8px;
}
.login-subtitle {
font-size: var(--tk-font-body-sm);
color: $tx2;
letter-spacing: 0.05em;
font-size: 14px;
color: $tx3;
}
/* ─── 装饰线 ─── */
.login-divider {
width: 48px;
margin-bottom: 40px;
/* ─── 输入框 ─── */
.login-field {
height: 56px;
background: $card;
border: 1.5px solid $bd;
border-radius: 16px;
display: flex;
align-items: center;
padding: 0 16px;
margin-bottom: 12px;
}
.login-divider-line {
height: 3px;
background: $pri;
border-radius: $r-xs;
opacity: 0.4;
.login-input {
flex: 1;
height: 100%;
font-size: 16px;
color: $tx;
}
.login-placeholder {
color: $tx3;
font-size: 16px;
}
.login-eye {
font-size: 14px;
color: var(--tk-pri);
font-weight: 500;
padding: 6px 0;
flex-shrink: 0;
}
/* ─── 登录按钮 ─── */
.login-body {
.login-submit {
height: 54px;
border-radius: 16px;
background: var(--tk-pri);
@include flex-center;
margin-top: 12px;
margin-bottom: 16px;
box-shadow: 0 4px 16px rgba($pri, 0.3);
&:active {
opacity: var(--tk-touch-feedback-opacity);
}
}
.login-submit-text {
font-size: 18px;
font-weight: 600;
color: $white;
}
/* ─── 分隔线 ─── */
.login-divider {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 16px;
}
.login-divider-line {
flex: 1;
height: 1px;
background: $bd-l;
}
.login-divider-text {
font-size: 12px;
color: $tx3;
}
/* ─── 微信登录 ─── */
.login-wechat-btn {
height: 54px;
border-radius: 16px;
background: $wechat;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 24px;
&:active {
opacity: var(--tk-touch-feedback-opacity);
}
}
.login-wechat-icon {
font-size: 16px;
color: $white;
font-weight: bold;
}
.login-wechat-text {
font-size: 18px;
font-weight: 600;
color: $white;
}
/* ─── 手机号绑定(微信登录后) ─── */
.login-bind-section {
width: 100%;
}
.login-btn {
.login-btn-bind {
width: 100%;
height: $btn-primary-h;
background: $pri;
height: 54px;
background: var(--tk-pri);
color: $white;
font-size: var(--tk-font-body-lg);
font-size: 18px;
font-weight: 600;
border-radius: $r;
border-radius: 16px;
border: none;
@include flex-center;
letter-spacing: 0.04em;
box-shadow: 0 4px 16px rgba($pri, 0.25);
padding: 0;
line-height: 1;
@@ -85,70 +173,62 @@
&::after {
border: none;
}
&--dev {
margin-top: 16px;
background: $wrn;
box-shadow: 0 4px 16px rgba($wrn, 0.2);
}
&:active {
opacity: 0.85;
}
}
/* ─── 协议 ─── */
.agreement-row {
display: flex;
align-items: flex-start;
margin-top: 28px;
gap: 10px;
width: 100%;
}
.agreement-check {
width: 28px;
height: 28px;
border: 2px solid $bd;
border-radius: $r-sm;
width: 24px;
height: 24px;
border: 1.5px solid $bd;
border-radius: 6px;
@include flex-center;
flex-shrink: 0;
margin-top: 2px;
transition: all 0.2s;
margin-top: 1px;
&.checked {
background: $pri;
border-color: $pri;
background: var(--tk-pri);
border-color: var(--tk-pri);
}
}
.agreement-check-mark {
font-size: var(--tk-font-body-sm);
font-size: 14px;
color: $white;
font-weight: bold;
line-height: 1;
}
.agreement-text {
font-size: var(--tk-font-cap);
color: $tx2;
line-height: 1.7;
font-size: 12px;
color: $tx3;
line-height: 1.8;
}
.agreement-link {
color: $pri;
color: var(--tk-pri);
font-weight: 500;
}
/* ─── 暂不登录 ─── */
.skip-row {
width: 100%;
/* ─── 开发模式 ─── */
.login-dev-btn {
text-align: center;
margin-top: 24px;
padding: 12px;
border: 1px dashed $bd;
border-radius: 12px;
margin-top: 16px;
&:active {
opacity: var(--tk-touch-feedback-opacity);
}
}
.skip-btn {
font-size: var(--tk-font-body-sm);
color: var(--tk-text-secondary);
padding: 8px 16px;
.login-dev-btn-text {
font-size: 13px;
color: $tx3;
}

View File

@@ -1,28 +1,25 @@
import { useState } from 'react';
import { View, Text, Button } from '@tarojs/components';
import { View, Text, Input, Button } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useAuthStore } from '../../stores/auth';
import { useElderClass } from '../../hooks/useElderClass';
import PageShell from '@/components/ui/PageShell';
import './index.scss';
const IS_DEV = process.env.NODE_ENV !== 'production';
// 运行时检测是否在 DevTools 模拟器中(弥补编译时 IS_DEV 在 production 构建中为 false 的问题)
const IS_SIMULATOR = typeof __wxConfig !== 'undefined' && (__wxConfig as any).envVersion !== 'release';
export default function Login() {
const modeClass = useElderClass();
const [needBind, setNeedBind] = useState(false);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [agreed, setAgreed] = useState(false);
const [needBind, setNeedBind] = useState(false);
const credentialLogin = useAuthStore((s) => s.credentialLogin);
const login = useAuthStore((s) => s.login);
const bindPhone = useAuthStore((s) => s.bindPhone);
const loading = useAuthStore((s) => s.loading);
const isMedicalStaff = useAuthStore((s) => s.isMedicalStaff);
// 登录页不应用关怀模式(正常模式尺寸已足够大)
const loginClass = '';
const navigateAfterLogin = () => {
if (isMedicalStaff()) {
Taro.reLaunch({ url: '/pages/pkg-doctor-core/index' });
@@ -31,6 +28,31 @@ export default function Login() {
}
};
const handleCredentialLogin = async () => {
if (!username.trim()) {
Taro.showToast({ title: '请输入账号', icon: 'none' });
return;
}
if (!password.trim()) {
Taro.showToast({ title: '请输入密码', icon: 'none' });
return;
}
if (!agreed) {
Taro.showToast({ title: '请先阅读并同意用户协议', icon: 'none' });
return;
}
try {
const success = await credentialLogin(username.trim(), password);
if (success) {
navigateAfterLogin();
} else {
Taro.showToast({ title: '账号或密码错误', icon: 'none' });
}
} catch {
Taro.showToast({ title: '登录失败,请重试', icon: 'none' });
}
};
const handleWechatLogin = async () => {
if (!agreed) {
Taro.showToast({ title: '请先阅读并同意用户协议', icon: 'none' });
@@ -51,24 +73,18 @@ export default function Login() {
}
};
/** Dev 模式快速登录:跳过 getPhoneNumber用 mock 数据直接调用绑定 API */
const handleDevQuickLogin = async () => {
try {
const success = await bindPhone('dev_mock_encrypted', 'dev_mock_iv');
const success = await credentialLogin('admin', 'Admin@2026');
if (success) {
navigateAfterLogin();
}
} catch (err: any) {
Taro.showToast({ title: err?.message || '绑定失败', icon: 'none' });
setNeedBind(false);
Taro.showToast({ title: err?.message || '登录失败', icon: 'none' });
}
};
const handleGetPhone = async (e: { detail: { errMsg: string; encryptedData: string; iv: string } }) => {
if (!agreed) {
Taro.showToast({ title: '请先阅读并同意用户协议', icon: 'none' });
return;
}
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
Taro.showToast({ title: '需要授权手机号', icon: 'none' });
return;
@@ -80,81 +96,115 @@ export default function Login() {
navigateAfterLogin();
}
} catch (err: any) {
const msg = err?.message || '绑定失败';
Taro.showModal({
title: '绑定手机号失败',
content: msg,
content: err?.message || '绑定失败',
confirmText: '重新登录',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
setNeedBind(false);
}
},
success: (res) => { if (res.confirm) setNeedBind(false); },
});
}
};
return (
<PageShell padding="none" scroll className={`login-page ${loginClass}`}>
{/* 品牌区 */}
<View className='login-brand'>
<View className='login-logo'>
<Text className='login-logo-mark'>+</Text>
<View className="login-page">
{/* 品牌区 */}
<View className="login-brand">
<View className="login-logo">
<Text className="login-logo-letter">H</Text>
</View>
<Text className="login-title">HMS </Text>
<Text className="login-subtitle"></Text>
</View>
{!needBind ? (
<>
{/* 账号输入 */}
<View className="login-field">
<Input
className="login-input"
type="text"
placeholder="请输入账号"
placeholderClass="login-placeholder"
value={username}
onInput={(e) => setUsername(e.detail.value)}
/>
</View>
<Text className='login-title'></Text>
<Text className='login-subtitle'></Text>
</View>
{/* 装饰线 */}
<View className='login-divider'>
<View className='login-divider-line' />
</View>
{/* 登录按钮 */}
<View className='login-body'>
{!needBind ? (
<Button className='login-btn' onClick={handleWechatLogin} loading={loading}>
</Button>
) : (
<>
<Button
className='login-btn'
openType='getPhoneNumber'
onGetPhoneNumber={handleGetPhone}
loading={loading}
>
</Button>
{(IS_DEV || IS_SIMULATOR) && (
<Button className='login-btn login-btn--dev' onClick={handleDevQuickLogin} loading={loading}>
</Button>
)}
</>
)}
</View>
{/* 协议 */}
<View className='agreement-row'>
<View className={`agreement-check ${agreed ? 'checked' : ''}`} onClick={() => setAgreed(!agreed)}>
{agreed && <Text className='agreement-check-mark'>&#10003;</Text>}
{/* 密码输入 */}
<View className="login-field">
<Input
className="login-input"
type="text"
password={!showPassword}
placeholder="请输入密码"
placeholderClass="login-placeholder"
value={password}
onInput={(e) => setPassword(e.detail.value)}
/>
<Text
className="login-eye"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? '隐藏' : '显示'}
</Text>
</View>
<Text className='agreement-text'>
<Text className='agreement-link' onClick={() => Taro.navigateTo({ url: '/pages/legal/user-agreement' })}></Text>
<Text className='agreement-link' onClick={() => Taro.navigateTo({ url: '/pages/legal/privacy-policy' })}></Text>
</Text>
</View>
{/* 暂不登录 */}
<View className='skip-row'>
<Text className='skip-btn' onClick={() => Taro.reLaunch({ url: '/pages/index/index' })}>
</Text>
{/* 登录按钮 */}
<View className="login-submit" onClick={handleCredentialLogin}>
<Text className="login-submit-text">{loading ? '登录中...' : '登录'}</Text>
</View>
{/* 分隔线 */}
<View className="login-divider">
<View className="login-divider-line" />
<Text className="login-divider-text"></Text>
<View className="login-divider-line" />
</View>
{/* 微信一键登录 */}
<View className="login-wechat-btn" onClick={handleWechatLogin}>
<Text className="login-wechat-icon"></Text>
<Text className="login-wechat-text"></Text>
</View>
</>
) : (
<View className="login-bind-section">
<Button
className="login-btn-bind"
openType="getPhoneNumber"
onGetPhoneNumber={handleGetPhone}
loading={loading}
>
</Button>
</View>
</PageShell>
)}
{/* 协议 */}
<View className="agreement-row">
<View
className={`agreement-check ${agreed ? 'checked' : ''}`}
onClick={() => setAgreed(!agreed)}
>
{agreed && <Text className="agreement-check-mark">&#10003;</Text>}
</View>
<Text className="agreement-text">
<Text className="agreement-link" onClick={() => Taro.navigateTo({ url: '/pages/legal/user-agreement' })}></Text>
<Text className="agreement-link" onClick={() => Taro.navigateTo({ url: '/pages/legal/privacy-policy' })}></Text>
</Text>
</View>
<View style={{ flex: 1 }} />
{/* 开发模式 */}
{(IS_DEV || IS_SIMULATOR) && (
<View className="login-dev-btn" onClick={handleDevQuickLogin}>
<Text className="login-dev-btn-text"> </Text>
</View>
)}
</View>
);
}

View File

@@ -2,27 +2,27 @@
@import '../../styles/mixins.scss';
.mall-page {
padding-bottom: calc(120px + env(safe-area-inset-bottom));
padding-bottom: calc(var(--tk-tabbar-space) + env(safe-area-inset-bottom));
}
/* ─── 积分余额卡片 ─── */
.mall-header {
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
padding: 48px 32px 36px;
background: linear-gradient(135deg, var(--tk-pri) 0%, var(--tk-pri-d) 100%);
padding: var(--tk-gap-2xl) var(--tk-gap-xl) var(--tk-gap-xl);
}
.points-card {
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: $r-lg;
padding: 32px;
padding: var(--tk-gap-xl);
}
.points-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
.points-label {
@@ -33,12 +33,12 @@
.checkin-btn {
background: rgba(255, 255, 255, 0.25);
border: 1px solid rgba(255, 255, 255, 0.4);
padding: 10px 28px;
padding: var(--tk-gap-sm) var(--tk-card-padding-lg);
border-radius: $r-pill;
transition: all 0.2s;
&:active {
opacity: 0.8;
opacity: var(--tk-touch-feedback-opacity);
}
&.checked {
@@ -63,7 +63,7 @@
font-weight: bold;
color: $white;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
letter-spacing: 2px;
line-height: 1;
}
@@ -77,13 +77,13 @@
/* ─── 商品类型切换 ─── */
.type-tabs {
display: flex;
padding: 20px 24px 0;
padding: var(--tk-section-gap) var(--tk-page-padding) 0;
}
.type-tab {
flex: 1;
text-align: center;
padding: 16px 0;
padding: var(--tk-gap-md) 0;
position: relative;
min-height: 48px;
@@ -95,7 +95,7 @@
transform: translateX(-50%);
width: 48px;
height: 4px;
background: $pri;
background: var(--tk-pri);
border-radius: $r-xs;
}
}
@@ -105,7 +105,7 @@
color: $tx2;
&.active {
color: $pri;
color: var(--tk-pri);
font-weight: 600;
}
}
@@ -114,15 +114,15 @@
.product-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
padding: 20px 24px;
gap: var(--tk-gap-md);
padding: var(--tk-section-gap) var(--tk-page-padding);
}
.product-card {
overflow: hidden;
&:active {
opacity: 0.7;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -131,7 +131,7 @@
height: 200px;
@include flex-center;
&.type-physical { background: $pri-l; }
&.type-physical { background: var(--tk-pri-l); }
&.type-service { background: $acc-l; }
&.type-privilege { background: $wrn-l; }
}
@@ -140,7 +140,7 @@
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-hero);
font-weight: bold;
color: $pri;
color: var(--tk-pri);
line-height: 1;
.type-service & { color: $acc; }
@@ -148,7 +148,7 @@
}
.product-info {
padding: 20px;
padding: var(--tk-section-gap);
}
.product-name {
@@ -156,7 +156,7 @@
font-weight: 600;
color: $tx;
display: block;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -171,7 +171,7 @@
.product-points {
display: flex;
align-items: center;
gap: 4px;
gap: var(--tk-gap-2xs);
}
.product-points-char {
@@ -190,7 +190,7 @@
.product-stock {
font-size: var(--tk-font-body);
padding: 2px 10px;
padding: 2px var(--tk-gap-sm);
border-radius: $r-sm;
&.out {

View File

@@ -3,13 +3,13 @@
.messages-page {
// PageShell 接管 min-height, background
padding: 20px 24px 100px;
padding-bottom: calc(100px + env(safe-area-inset-bottom));
padding: var(--tk-section-gap) var(--tk-page-padding) var(--tk-tabbar-space);
padding-bottom: calc(var(--tk-tabbar-space) + env(safe-area-inset-bottom));
}
/* ─── 页头 ─── */
.messages-header {
margin-bottom: 20px;
margin-bottom: var(--tk-section-gap);
}
.messages-title {
@@ -26,7 +26,7 @@
background: $surface-alt;
border-radius: $r-sm;
padding: 3px;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.msg-segment-tab {
@@ -37,7 +37,7 @@
position: relative;
&:active {
opacity: 0.85;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -82,13 +82,13 @@
.msg-list {
display: flex;
flex-direction: column;
gap: 8px;
gap: var(--tk-gap-xs);
}
/* ─── 咨询卡片 ─── */
.consult-card {
display: flex;
gap: 12px;
gap: var(--tk-gap-sm);
align-items: center;
// ContentCard 接管 background, border-radius, padding, box-shadow, active feedback
}
@@ -107,7 +107,7 @@
}
.consult-avatar-active {
background: $pri-l;
background: var(--tk-pri-l);
}
.consult-avatar-char {
@@ -118,7 +118,7 @@
}
.consult-avatar-active .consult-avatar-char {
color: $pri;
color: var(--tk-pri);
}
.consult-body {
@@ -130,7 +130,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
&:last-child {
margin-bottom: 0;
@@ -156,7 +156,7 @@
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
margin-right: 8px;
margin-right: var(--tk-gap-xs);
}
.consult-badge {
@@ -178,7 +178,7 @@
/* ─── 通知卡片 ─── */
.notify-card {
display: flex;
gap: 12px;
gap: var(--tk-gap-sm);
align-items: flex-start;
// ContentCard 接管 background, border-radius, padding, box-shadow
}
@@ -203,8 +203,8 @@
.notify-type-appointment,
.notify-type-points {
background: $pri-l;
color: $pri;
background: var(--tk-pri-l);
color: var(--tk-pri);
}
.notify-type-alert {
@@ -227,7 +227,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
}
.notify-title {
@@ -244,7 +244,7 @@
font-size: var(--tk-font-micro);
color: var(--tk-text-secondary);
flex-shrink: 0;
margin-left: 8px;
margin-left: var(--tk-gap-xs);
}
.notify-desc {
@@ -257,7 +257,7 @@
width: 8px;
height: 8px;
border-radius: $r-xs;
background: $pri;
background: var(--tk-pri);
flex-shrink: 0;
margin-top: 6px;
margin-top: var(--tk-gap-2xs);
}

View File

@@ -12,7 +12,7 @@
.chat-header {
display: flex;
align-items: center;
padding: 12px 16px;
padding: var(--tk-gap-sm) var(--tk-gap-md);
background: $card;
border-bottom: 1px solid $bd-l;
flex-shrink: 0;
@@ -25,13 +25,13 @@
z-index: 1;
&:active {
opacity: 0.7;
opacity: var(--tk-touch-feedback-opacity);
}
}
.chat-header__back-text {
font-size: var(--tk-font-body-sm);
color: $pri;
color: var(--tk-pri);
}
.chat-header__center {
@@ -60,14 +60,14 @@
/* ─── 消息区 ─── */
.chat-messages {
flex: 1;
padding: 16px 16px 0;
padding: var(--tk-gap-md) var(--tk-gap-md) 0;
overflow-y: auto;
}
.msg-row {
display: flex;
margin-bottom: 16px;
gap: 8px;
margin-bottom: var(--tk-gap-md);
gap: var(--tk-gap-xs);
&--self {
justify-content: flex-end;
@@ -79,7 +79,7 @@
width: 32px;
height: 32px;
border-radius: $r;
background: $pri-l;
background: var(--tk-pri-l);
@include flex-center;
flex-shrink: 0;
}
@@ -88,13 +88,13 @@
@include serif-number;
font-size: var(--tk-font-cap);
font-weight: 700;
color: $pri;
color: var(--tk-pri);
}
/* ─── 消息气泡 ─── */
.msg-bubble {
max-width: 70%;
padding: 12px 16px;
padding: var(--tk-gap-sm) var(--tk-gap-md);
box-shadow: $shadow-sm;
&--other {
@@ -103,7 +103,7 @@
}
&--self {
background: $pri;
background: var(--tk-pri);
border-radius: $r $r $r-xs $r;
}
}
@@ -123,7 +123,7 @@
.msg-date-divider {
display: flex;
justify-content: center;
padding: 12px 0;
padding: var(--tk-gap-sm) 0;
&__text {
font-size: var(--tk-font-micro);
@@ -137,7 +137,7 @@
.msg-truncated-hint {
display: flex;
justify-content: center;
padding: 12px 0;
padding: var(--tk-gap-sm) 0;
&__text {
font-size: var(--tk-font-micro);
@@ -151,14 +151,14 @@
.msg-image {
width: 200px;
border-radius: $r-sm;
margin-top: 4px;
margin-top: var(--tk-gap-2xs);
}
.msg-time {
font-size: var(--tk-font-micro);
color: var(--tk-text-secondary);
display: block;
margin-top: 4px;
margin-top: var(--tk-gap-2xs);
.msg-bubble--self & {
color: rgba(255, 255, 255, 0.7);
@@ -168,7 +168,7 @@
.chat-empty {
text-align: center;
padding: 80px 24px;
padding: 80px var(--tk-page-padding);
&__text {
font-size: var(--tk-font-cap);
@@ -202,10 +202,10 @@
width: 48px;
height: 48px;
border-radius: $r-lg;
background: $pri;
background: var(--tk-pri);
@include flex-center;
flex-shrink: 0;
box-shadow: 0 2px 6px rgba(196, 98, 58, 0.3);
box-shadow: 0 2px 6px rgba($pri, 0.3);
&--disabled {
opacity: 0.5;
@@ -219,7 +219,7 @@
}
.chat-closed-bar {
padding: 16px;
padding: var(--tk-gap-md);
text-align: center;
background: $card;
border-top: 1px solid $bd-l;

View File

@@ -2,12 +2,12 @@
@import '../../../../styles/mixins.scss';
.alert-detail-header {
margin-bottom: 24px;
margin-bottom: var(--tk-gap-lg);
&__tags {
display: flex;
gap: 12px;
margin-bottom: 12px;
gap: var(--tk-gap-sm);
margin-bottom: var(--tk-gap-sm);
}
&__time {
@@ -19,7 +19,7 @@
.detail-severity {
font-size: var(--tk-font-h2);
font-weight: 600;
padding: 6px 16px;
padding: var(--tk-gap-2xs) var(--tk-gap-md);
border-radius: $r-sm;
&--info {
@@ -45,7 +45,7 @@
.detail-status {
font-size: var(--tk-font-h2);
padding: 6px 16px;
padding: var(--tk-gap-2xs) var(--tk-gap-md);
border-radius: $r-sm;
&--pending {
@@ -54,8 +54,8 @@
}
&--acknowledged {
background: $pri-l;
color: $pri;
background: var(--tk-pri-l);
color: var(--tk-pri);
}
&--resolved {
@@ -73,7 +73,7 @@
&__label {
font-size: var(--tk-font-h2);
color: $tx2;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
&__value {
@@ -94,18 +94,18 @@
line-height: 1.6;
white-space: pre-wrap;
background: $bg;
padding: 16px;
padding: var(--tk-gap-md);
border-radius: $r;
margin-top: 8px;
margin-top: var(--tk-gap-xs);
}
}
}
.alert-detail-actions {
display: flex;
gap: 16px;
margin-top: 32px;
padding: 0 8px;
gap: var(--tk-gap-md);
margin-top: var(--tk-gap-xl);
padding: 0 var(--tk-gap-xs);
}
.alert-action-btn {
@@ -118,7 +118,7 @@
text-align: center;
&--primary {
background: $pri;
background: var(--tk-pri);
color: $card;
border: none;

View File

@@ -9,7 +9,7 @@ import {
import Loading from '@/components/Loading';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import { useElderClass } from '../../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import './index.scss';
const SEVERITY_MAP: Record<string, { label: string; className: string }> = {
@@ -27,7 +27,7 @@ const STATUS_MAP: Record<string, { label: string; className: string }> = {
};
export default function AlertDetail() {
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [alert, setAlert] = useState<Alert | null>(null);
const [loading, setLoading] = useState(true);
const [actionLoading, setActionLoading] = useState(false);

View File

@@ -10,7 +10,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
.alert-list-title {
@@ -51,14 +51,14 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.alert-card__title {
font-size: var(--tk-font-body-lg);
font-weight: 500;
color: $tx;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.alert-card__footer {

View File

@@ -11,7 +11,7 @@ import PaginationBar from '@/components/patterns/PaginationBar';
import SearchSection from '@/components/patterns/SearchSection';
import ErrorState from '@/components/ErrorState';
import EmptyState from '@/components/EmptyState';
import { useElderClass } from '../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import { safeNavigateTo } from '@/utils/navigate';
import './index.scss';
@@ -51,7 +51,7 @@ const STATUS_FILTERS = [
];
export default function AlertList() {
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [alerts, setAlerts] = useState<Alert[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);

View File

@@ -6,7 +6,7 @@
/* ─── 表单分组间距ContentCard 外层补充) ─── */
.section {
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
.section-title {
@@ -14,14 +14,14 @@
font-weight: bold;
color: $tx;
display: block;
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
.form-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0;
padding: var(--tk-gap-md) 0;
border-bottom: 1px solid $bd-l;
&:last-child {
@@ -59,24 +59,24 @@
.form-textarea {
width: 100%;
margin-top: 12px;
margin-top: var(--tk-gap-sm);
font-size: var(--tk-font-h1);
color: $tx;
min-height: 120px;
background: $bg;
border-radius: $r-sm;
padding: 16px;
padding: var(--tk-gap-md);
}
.submit-btn {
background: $pri;
background: var(--tk-pri);
border-radius: $r-sm;
padding: 24px;
padding: var(--tk-gap-lg);
text-align: center;
margin-top: 24px;
margin-top: var(--tk-gap-lg);
&:active {
background: $pri-d;
opacity: var(--tk-touch-feedback-opacity);
}
&--disabled {

View File

@@ -7,7 +7,7 @@ import {
import Loading from '@/components/Loading';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import { useElderClass } from '../../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import { useSafeTimeout } from '@/hooks/useSafeTimeout';
import './index.scss';
@@ -61,7 +61,7 @@ export default function DialysisCreate() {
const version = router.params.version ? Number(router.params.version) : 0;
const patientIdFromRoute = router.params.patientId || '';
const isEdit = !!id;
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [form, setForm] = useState<FormState>({ ...initialForm, patient_id: patientIdFromRoute });
const [loading, setLoading] = useState(isEdit);

View File

@@ -6,14 +6,14 @@
font-weight: bold;
color: $tx;
display: block;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.record-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.record-header__title {
@@ -25,15 +25,15 @@
.record-header__status {
display: inline-block;
padding: 4px 12px;
padding: var(--tk-gap-2xs) var(--tk-gap-sm);
border-radius: $r-xs;
font-size: var(--tk-font-body);
background: $bd-l;
color: $tx3;
&--completed {
background: $pri-l;
color: $pri;
background: var(--tk-pri-l);
color: var(--tk-pri);
}
&--reviewed {
@@ -52,14 +52,14 @@
font-size: var(--tk-font-h2);
color: $tx3;
display: block;
margin-top: 8px;
margin-top: var(--tk-gap-xs);
font-variant-numeric: tabular-nums;
}
.detail-row {
display: flex;
justify-content: space-between;
padding: 10px 0;
padding: var(--tk-gap-sm) 0;
border-bottom: 1px solid $bd-l;
&:last-child {
@@ -77,13 +77,13 @@
color: $tx;
text-align: right;
flex: 1;
margin-left: 24px;
margin-left: var(--tk-gap-lg);
font-variant-numeric: tabular-nums;
}
.error-text {
text-align: center;
padding: 120px 0;
padding: var(--tk-gap-2xl) 0;
font-size: var(--tk-font-body-lg);
color: $tx3;
}
@@ -91,24 +91,24 @@
.actions {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px 0;
gap: var(--tk-gap-sm);
padding: var(--tk-gap-md) 0;
}
.action-btn {
border-radius: $r-sm;
padding: 20px;
padding: var(--tk-section-gap);
text-align: center;
&--primary {
background: $pri;
background: var(--tk-pri);
.action-btn__text {
color: $white;
}
&:active {
background: $pri-d;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -117,7 +117,7 @@
border: 1px solid $bd;
.action-btn__text {
color: $pri;
color: var(--tk-pri);
}
}

View File

@@ -10,14 +10,14 @@ import {
import Loading from '@/components/Loading';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import { useElderClass } from '../../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import { useSafeTimeout } from '@/hooks/useSafeTimeout';
import './index.scss';
export default function DialysisDetail() {
const router = useRouter();
const id = router.params.id || '';
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [record, setRecord] = useState<DialysisRecord | null>(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);

View File

@@ -7,7 +7,7 @@
// PaginationBar 已接管pagination 分页样式
.record-count {
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
text {
font-size: var(--tk-font-h2);
@@ -30,12 +30,12 @@
.type-tag {
display: inline-block;
padding: 4px 12px;
padding: var(--tk-gap-2xs) var(--tk-gap-sm);
border-radius: $r-xs;
font-size: var(--tk-font-body);
font-weight: 600;
background: $pri-l;
color: $pri-d;
background: var(--tk-pri-l);
color: var(--tk-pri-d);
&--hdf {
background: $acc-l;
@@ -51,7 +51,7 @@
.record-card__body {
display: flex;
flex-wrap: wrap;
gap: 12px;
gap: var(--tk-gap-sm);
}
.record-card__date {
@@ -68,12 +68,12 @@
.fab {
position: fixed;
right: 32px;
bottom: 120px;
right: var(--tk-gap-xl);
bottom: calc(var(--tk-gap-2xl) + var(--tk-gap-xl));
width: 96px;
height: 96px;
border-radius: $r-pill;
background: $pri;
background: var(--tk-pri);
display: flex;
align-items: center;
justify-content: center;
@@ -81,7 +81,7 @@
z-index: 10;
&:active {
background: $pri-d;
opacity: var(--tk-touch-feedback-opacity);
}
}

View File

@@ -13,7 +13,7 @@ import PaginationBar from '@/components/patterns/PaginationBar';
import SegmentTabs from '@/components/SegmentTabs';
import ErrorState from '@/components/ErrorState';
import EmptyState from '@/components/EmptyState';
import { useElderClass } from '../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import { safeNavigateTo } from '@/utils/navigate';
import './index.scss';
@@ -35,7 +35,7 @@ const STATUS_LABEL: Record<string, string> = {
export default function DialysisList() {
const router = useRouter();
const patientId = router.params.patientId || '';
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [searchPatient, setSearchPatient] = useState('');
const [currentPatientId, setCurrentPatientId] = useState(patientId);
const [activeTab, setActiveTab] = useState('');

View File

@@ -6,7 +6,7 @@
/* ─── 表单分组间距ContentCard 外层补充) ─── */
.section {
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
.section-title {
@@ -14,14 +14,14 @@
font-weight: bold;
color: $tx;
display: block;
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
.form-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0;
padding: var(--tk-gap-md) 0;
border-bottom: 1px solid $bd-l;
&:last-child {
@@ -54,24 +54,24 @@
.form-textarea {
width: 100%;
margin-top: 12px;
margin-top: var(--tk-gap-sm);
font-size: var(--tk-font-h1);
color: $tx;
min-height: 120px;
background: $bg;
border-radius: $r-sm;
padding: 16px;
padding: var(--tk-gap-md);
}
.submit-btn {
background: $pri;
background: var(--tk-pri);
border-radius: $r-sm;
padding: 24px;
padding: var(--tk-gap-lg);
text-align: center;
margin-top: 24px;
margin-top: var(--tk-gap-lg);
&:active {
background: $pri-d;
opacity: var(--tk-touch-feedback-opacity);
}
&--disabled {

View File

@@ -5,7 +5,7 @@ import { createDialysisPrescription } from '@/services/doctor/dialysis';
import Loading from '@/components/Loading';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import { useElderClass } from '../../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import { useSafeTimeout } from '@/hooks/useSafeTimeout';
import './index.scss';
@@ -54,7 +54,7 @@ const initialForm: FormState = {
export default function PrescriptionCreate() {
const router = useRouter();
const patientId = router.params.patientId || '';
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [form, setForm] = useState<FormState>(initialForm);
const [submitting, setSubmitting] = useState(false);
const { safeSetTimeout } = useSafeTimeout();

View File

@@ -6,14 +6,14 @@
font-weight: bold;
color: $tx;
display: block;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.rx-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.rx-header__title {
@@ -24,7 +24,7 @@
.rx-header__status {
display: inline-block;
padding: 4px 12px;
padding: var(--tk-gap-2xs) var(--tk-gap-sm);
border-radius: $r-xs;
font-size: var(--tk-font-body);
background: $bd-l;
@@ -46,7 +46,7 @@
.detail-row {
display: flex;
justify-content: space-between;
padding: 10px 0;
padding: var(--tk-gap-sm) 0;
border-bottom: 1px solid $bd-l;
&:last-child {
@@ -64,7 +64,7 @@
color: $tx;
text-align: right;
flex: 1;
margin-left: 24px;
margin-left: var(--tk-gap-lg);
font-variant-numeric: tabular-nums;
}
@@ -76,7 +76,7 @@
.error-text {
text-align: center;
padding: 120px 0;
padding: var(--tk-gap-2xl) 0;
font-size: var(--tk-font-body-lg);
color: $tx3;
}
@@ -84,13 +84,13 @@
.actions {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px 0;
gap: var(--tk-gap-sm);
padding: var(--tk-gap-md) 0;
}
.action-btn {
border-radius: $r-sm;
padding: 20px;
padding: var(--tk-section-gap);
text-align: center;
&--secondary {
@@ -98,7 +98,7 @@
border: 1px solid $bd;
.action-btn__text {
color: $pri;
color: var(--tk-pri);
}
}

View File

@@ -9,14 +9,14 @@ import {
import Loading from '@/components/Loading';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import { useElderClass } from '../../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import { useSafeTimeout } from '@/hooks/useSafeTimeout';
import './index.scss';
export default function PrescriptionDetail() {
const router = useRouter();
const id = router.params.id || '';
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [rx, setRx] = useState<DialysisPrescription | null>(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);

View File

@@ -7,7 +7,7 @@
// PaginationBar 已接管pagination 分页样式
.prescription-count {
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
text {
font-size: var(--tk-font-h2);
@@ -25,7 +25,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.prescription-card__model {
@@ -36,8 +36,8 @@
.prescription-card__body {
display: flex;
gap: 16px;
margin-bottom: 8px;
gap: var(--tk-gap-md);
margin-bottom: var(--tk-gap-xs);
}
.prescription-card__meta {
@@ -55,12 +55,12 @@
.fab {
position: fixed;
right: 32px;
bottom: 120px;
right: var(--tk-gap-xl);
bottom: calc(var(--tk-gap-2xl) + var(--tk-gap-xl));
width: 96px;
height: 96px;
border-radius: $r-pill;
background: $pri;
background: var(--tk-pri);
display: flex;
align-items: center;
justify-content: center;
@@ -68,7 +68,7 @@
z-index: 10;
&:active {
background: $pri-d;
opacity: var(--tk-touch-feedback-opacity);
}
}

View File

@@ -13,7 +13,7 @@ import PaginationBar from '@/components/patterns/PaginationBar';
import SegmentTabs from '@/components/SegmentTabs';
import ErrorState from '@/components/ErrorState';
import EmptyState from '@/components/EmptyState';
import { useElderClass } from '../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import { safeNavigateTo } from '@/utils/navigate';
import './index.scss';
@@ -31,7 +31,7 @@ const STATUS_LABEL: Record<string, string> = {
export default function PrescriptionList() {
const router = useRouter();
const patientId = router.params.patientId || '';
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [searchPatient, setSearchPatient] = useState('');
const [currentPatientId, setCurrentPatientId] = useState(patientId);
const [activeTab, setActiveTab] = useState('');

View File

@@ -9,7 +9,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
&__type {
font-family: 'Georgia', 'Times New Roman', serif;
@@ -20,7 +20,7 @@
&__status {
font-size: var(--tk-font-h2);
padding: 6px 16px;
padding: 6px var(--tk-gap-md);
border-radius: $r;
font-weight: 500;
@@ -39,7 +39,7 @@
font-size: var(--tk-font-h2);
color: $acc;
display: block;
margin-top: 8px;
margin-top: var(--tk-gap-xs);
}
.indicator-table {
@@ -48,19 +48,19 @@
.indicator-row {
display: flex;
padding: 16px 0;
padding: var(--tk-gap-md) 0;
border-bottom: 1px solid $bd-l;
align-items: center;
&--header {
border-bottom: 2px solid $bd;
padding-bottom: 12px;
padding-bottom: var(--tk-gap-sm);
}
&--abnormal {
background: $dan-l;
margin: 0 -12px;
padding: 16px 12px;
margin: 0 calc(-1 * var(--tk-gap-sm));
padding: var(--tk-gap-md) var(--tk-gap-sm);
border-radius: $r-sm;
}
}
@@ -108,9 +108,9 @@
}
.notes-display {
background: $pri-l;
background: var(--tk-pri-l);
border-radius: $r;
padding: 20px;
padding: var(--tk-section-gap);
}
.notes-text {
@@ -124,18 +124,18 @@
min-height: 200px;
background: $bd-l;
border-radius: $r;
padding: 20px;
padding: var(--tk-section-gap);
font-size: var(--tk-font-h1);
color: $tx;
box-sizing: border-box;
line-height: 1.6;
margin-bottom: 20px;
margin-bottom: var(--tk-section-gap);
}
.review-btn {
background: $pri;
background: var(--tk-pri);
border-radius: $r;
padding: 20px;
padding: var(--tk-section-gap);
text-align: center;
&--disabled {
@@ -151,7 +151,7 @@
.error-text {
text-align: center;
padding: 80px 32px;
padding: 80px var(--tk-gap-xl);
color: $tx3;
font-size: var(--tk-font-body-lg);
}

View File

@@ -6,14 +6,14 @@ import { getLabReport, reviewLabReport, type LabReportDetail } from '@/services/
import Loading from '@/components/Loading';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import { useElderClass } from '../../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import './index.scss';
export default function ReportDetail() {
const router = useRouter();
const patientId = router.params.patientId || '';
const reportId = router.params.id || '';
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [report, setReport] = useState<LabReportDetail | null>(null);
const [loading, setLoading] = useState(true);
const [doctorNotes, setDoctorNotes] = useState('');

View File

@@ -6,7 +6,7 @@
// StatusTag 已接管reviewed 标签样式
.report-count {
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
text {
font-size: var(--tk-font-h2);
@@ -24,7 +24,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.report-card__type {
@@ -42,7 +42,7 @@
.report-card__indicators {
display: flex;
align-items: center;
gap: 16px;
gap: var(--tk-gap-md);
}
.report-card__abnormal {

View File

@@ -11,13 +11,13 @@ import LoadingCard from '@/components/ui/LoadingCard';
import SearchSection from '@/components/patterns/SearchSection';
import ErrorState from '@/components/ErrorState';
import EmptyState from '@/components/EmptyState';
import { useElderClass } from '../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import './index.scss';
export default function ReportList() {
const router = useRouter();
const patientId = router.params.patientId || '';
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [searchPatient, setSearchPatient] = useState('');
const [currentPatientId, setCurrentPatientId] = useState(patientId);
const [reports, setReports] = useState<LabReportItem[]>([]);

View File

@@ -6,28 +6,28 @@
.inbox-list {
height: calc(100vh - 50px);
padding: 12px;
padding: var(--tk-gap-sm);
}
.inbox-card {
margin-bottom: 10px;
margin-bottom: var(--tk-gap-sm);
.inbox-card-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
gap: var(--tk-gap-xs);
margin-bottom: var(--tk-gap-2xs);
}
.inbox-type-tag {
color: $card;
font-size: var(--tk-font-micro);
padding: 2px 6px;
padding: 2px var(--tk-gap-2xs);
border-radius: $r-xs;
flex-shrink: 0;
&--ai {
background: $pri;
background: var(--tk-pri);
}
&--alert {
@@ -75,7 +75,7 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
padding: var(--tk-gap-md) var(--tk-section-gap);
border-bottom: 1px solid $bd-l;
.dialog-title {
@@ -91,21 +91,21 @@
}
.dialog-body {
padding: 16px 20px;
padding: var(--tk-gap-md) var(--tk-section-gap);
}
.dialog-patient {
font-size: var(--tk-font-cap);
color: $tx2;
display: block;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.thread-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 6px 0;
gap: var(--tk-gap-sm);
padding: var(--tk-gap-2xs) 0;
}
.thread-dot {
@@ -136,19 +136,19 @@
.dialog-actions {
display: flex;
gap: 8px;
padding: 12px 20px 20px;
gap: var(--tk-gap-xs);
padding: var(--tk-gap-sm) var(--tk-section-gap) var(--tk-section-gap);
border-top: 1px solid $bd-l;
.action-btn {
flex: 1;
text-align: center;
padding: 10px;
padding: var(--tk-gap-sm);
border-radius: $r-sm;
font-size: var(--tk-font-cap);
font-weight: 500;
&.primary { background: $pri; color: $card; }
&.primary { background: var(--tk-pri); color: $card; }
&.danger { background: $dan; color: $card; }
&.default { background: $surface-alt; color: $tx2; }
}

View File

@@ -15,7 +15,7 @@ import EmptyState from '@/components/EmptyState';
import SegmentTabs from '@/components/SegmentTabs';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import { useElderClass } from '../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import './index.scss';
const TYPE_LABEL: Record<string, string> = {
@@ -40,7 +40,7 @@ const STATUS_TABS = [
];
export default function ActionInboxPage() {
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [items, setItems] = useState<ActionItem[]>([]);
const [total, setTotal] = useState(0);
const [_page, setPage] = useState(1);

View File

@@ -12,7 +12,7 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 32px;
padding: var(--tk-gap-lg) var(--tk-gap-xl);
background: $card;
border-bottom: 1px solid $bd;
@@ -26,19 +26,19 @@
&__close {
font-size: var(--tk-font-h1);
color: $dan;
padding: 8px 16px;
padding: var(--tk-gap-xs) var(--tk-gap-md);
}
}
.chat-messages {
flex: 1;
padding: 24px;
padding: var(--tk-gap-lg);
overflow-y: auto;
}
.msg-row {
display: flex;
margin-bottom: 20px;
margin-bottom: var(--tk-section-gap);
&--self {
justify-content: flex-end;
@@ -47,7 +47,7 @@
.msg-bubble {
max-width: 70%;
padding: 20px 24px;
padding: var(--tk-section-gap) var(--tk-gap-lg);
border-radius: $r-lg;
position: relative;
@@ -57,7 +57,7 @@
}
&--self {
background: $pri;
background: var(--tk-pri);
border-top-right-radius: 4px;
}
}
@@ -77,13 +77,13 @@
.msg-truncated-hint {
display: flex;
justify-content: center;
padding: 12px 0;
padding: var(--tk-gap-sm) 0;
&__text {
font-size: var(--tk-font-body);
color: $tx3;
background: $bd-l;
padding: 4px 16px;
padding: var(--tk-gap-2xs) var(--tk-gap-md);
border-radius: $r;
}
}
@@ -93,7 +93,7 @@
font-size: var(--tk-font-body);
color: $tx3;
display: block;
margin-top: 8px;
margin-top: var(--tk-gap-xs);
text-align: right;
.msg-bubble--self & {
@@ -103,7 +103,7 @@
.chat-empty {
text-align: center;
padding: 120px 32px;
padding: var(--tk-gap-2xl) var(--tk-gap-xl);
&__text {
font-size: var(--tk-font-h1);
@@ -114,7 +114,7 @@
.chat-input-bar {
display: flex;
align-items: center;
padding: 16px 24px;
padding: var(--tk-gap-md) var(--tk-gap-lg);
background: $card;
border-top: 1px solid $bd;
padding-bottom: calc(16px + env(safe-area-inset-bottom));
@@ -124,15 +124,15 @@
flex: 1;
background: $bd-l;
border-radius: $r;
padding: 16px 20px;
padding: var(--tk-gap-md) var(--tk-section-gap);
font-size: var(--tk-font-body-lg);
margin-right: 16px;
margin-right: var(--tk-gap-md);
}
.chat-send-btn {
background: $pri;
background: var(--tk-pri);
border-radius: $r;
padding: 16px 28px;
padding: var(--tk-gap-md) var(--tk-card-padding-lg);
flex-shrink: 0;
&--disabled {
@@ -147,7 +147,7 @@
}
.chat-closed-bar {
padding: 24px;
padding: var(--tk-gap-lg);
text-align: center;
background: $card;
border-top: 1px solid $bd;

View File

@@ -7,7 +7,7 @@ import {
type ConsultationSession, type ConsultationMessage,
} from '@/services/doctor/consultation';
import Loading from '@/components/Loading';
import { useElderClass } from '@/hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import { useLongPolling } from '@/hooks/useLongPolling';
import './index.scss';
@@ -17,7 +17,7 @@ const MAX_STATE_MESSAGES = 300;
export default function ConsultationDetail() {
const router = useRouter();
const sessionId = router.params.id || '';
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [session, setSession] = useState<ConsultationSession | null>(null);
const [messages, setMessages] = useState<ConsultationMessage[]>([]);
const [inputText, setInputText] = useState('');

View File

@@ -17,7 +17,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.session-card__subject {
@@ -28,18 +28,18 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 16px;
margin-right: var(--tk-gap-md);
}
.session-card__info {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 8px;
gap: var(--tk-gap-md);
margin-bottom: var(--tk-gap-xs);
}
.session-card__type {
@include tag($pri-l, $pri);
@include tag(var(--tk-pri-l), var(--tk-pri));
}
.session-card__time {
@@ -58,8 +58,8 @@
.session-card__badge {
position: absolute;
top: 20px;
right: 20px;
top: var(--tk-section-gap);
right: var(--tk-section-gap);
min-width: 36px;
height: 36px;
background: $dan;

View File

@@ -11,7 +11,7 @@ import PaginationBar from '@/components/patterns/PaginationBar';
import SearchSection from '@/components/patterns/SearchSection';
import ErrorState from '@/components/ErrorState';
import EmptyState from '@/components/EmptyState';
import { useElderClass } from '../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import { formatDateTime } from '@/utils/date';
import { safeNavigateTo } from '@/utils/navigate';
import './index.scss';
@@ -30,7 +30,7 @@ const STATUS_COLOR_MAP: Record<string, 'success' | 'warning' | 'default' | 'info
};
export default function ConsultationList() {
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [sessions, setSessions] = useState<ConsultationSession[]>([]);
const [activeTab, setActiveTab] = useState('');
const [loading, setLoading] = useState(true);

View File

@@ -9,7 +9,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
margin-bottom: var(--tk-section-gap);
&__title {
font-family: 'Georgia', 'Times New Roman', serif;
@@ -20,12 +20,12 @@
&__status {
font-size: var(--tk-font-h2);
padding: 6px 16px;
padding: 6px var(--tk-gap-md);
border-radius: $r;
font-weight: 500;
&--pending { background: $wrn-l; color: $wrn; }
&--in_progress { background: $pri-l; color: $pri; }
&--in_progress { background: var(--tk-pri-l); color: var(--tk-pri); }
&--completed { background: $acc-l; color: $acc; }
&--overdue { background: $dan-l; color: $dan; }
&--cancelled { background: $bd-l; color: $tx3; }
@@ -35,13 +35,13 @@
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 16px;
gap: var(--tk-gap-md);
}
.info-item {
display: flex;
flex-direction: column;
gap: 4px;
gap: var(--tk-gap-2xs);
}
.info-label {
@@ -56,8 +56,8 @@
}
.task-template {
margin-top: 16px;
padding: 16px;
margin-top: var(--tk-gap-md);
padding: var(--tk-gap-md);
background: $bd-l;
border-radius: $r;
@@ -65,7 +65,7 @@
font-size: var(--tk-font-body);
color: $tx2;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
&__text {
@@ -76,7 +76,7 @@
}
.record-item {
padding: 20px 0;
padding: var(--tk-section-gap) 0;
border-bottom: 1px solid $bd-l;
&:last-child {
@@ -87,31 +87,31 @@
font-size: var(--tk-font-body);
color: $tx3;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
&__text {
font-size: var(--tk-font-h1);
color: $tx;
display: block;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
line-height: 1.5;
}
}
.start-btn {
text-align: center;
padding: 16px;
background: $pri;
padding: var(--tk-gap-md);
background: var(--tk-pri);
border-radius: $r;
margin-bottom: 24px;
margin-bottom: var(--tk-gap-lg);
color: $card;
font-size: var(--tk-font-body-lg);
font-weight: 500;
}
.form-group {
margin-bottom: 24px;
margin-bottom: var(--tk-gap-lg);
}
.form-label {
@@ -119,7 +119,7 @@
color: $tx2;
font-weight: 500;
display: block;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.form-textarea {
@@ -127,7 +127,7 @@
min-height: 160px;
background: $bd-l;
border-radius: $r;
padding: 16px 20px;
padding: var(--tk-gap-md) var(--tk-section-gap);
font-size: var(--tk-font-h1);
color: $tx;
box-sizing: border-box;
@@ -136,7 +136,7 @@
.form-date {
width: 100%;
padding: 16px 20px;
padding: var(--tk-gap-md) var(--tk-section-gap);
background: $bd-l;
border-radius: $r;
font-size: var(--tk-font-h1);
@@ -145,11 +145,11 @@
}
.submit-btn {
background: $pri;
background: var(--tk-pri);
border-radius: $r;
padding: 20px;
padding: var(--tk-section-gap);
text-align: center;
margin-top: 16px;
margin-top: var(--tk-gap-md);
&--disabled {
opacity: 0.5;
@@ -164,7 +164,7 @@
.error-text {
text-align: center;
padding: 80px 32px;
padding: 80px var(--tk-gap-xl);
color: $tx3;
font-size: var(--tk-font-body-lg);
}

View File

@@ -10,7 +10,7 @@ import {
import Loading from '@/components/Loading';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import { useElderClass } from '../../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import './index.scss';
const STATUS_LABELS: Record<string, string> = {
@@ -24,7 +24,7 @@ const STATUS_LABELS: Record<string, string> = {
export default function FollowUpDetail() {
const router = useRouter();
const taskId = router.params.id || '';
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [task, setTask] = useState<FollowUpTask | null>(null);
const [records, setRecords] = useState<FollowUpRecord[]>([]);
const [loading, setLoading] = useState(true);

View File

@@ -6,7 +6,7 @@
// StatusTag 已接管:任务状态标签
.task-count {
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
text {
font-size: var(--tk-font-h2);
@@ -24,7 +24,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.task-card__type {
@@ -37,7 +37,7 @@
font-size: var(--tk-font-h1);
color: $tx2;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.task-card__footer {

View File

@@ -10,7 +10,7 @@ import LoadingCard from '@/components/ui/LoadingCard';
import SearchSection from '@/components/patterns/SearchSection';
import ErrorState from '@/components/ErrorState';
import EmptyState from '@/components/EmptyState';
import { useElderClass } from '../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import './index.scss';
const TABS = [
@@ -31,7 +31,7 @@ const STATUS_COLOR_MAP: Record<string, 'warning' | 'info' | 'success' | 'error'>
export default function FollowUpList() {
const router = useRouter();
const patientId = router.params.patientId || '';
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [tasks, setTasks] = useState<FollowUpTask[]>([]);
const [activeTab, setActiveTab] = useState('');
const [loading, setLoading] = useState(true);

View File

@@ -6,19 +6,19 @@
.doctor-home {
&__header {
margin-bottom: 40px;
margin-bottom: var(--tk-gap-2xl);
}
&__title {
@include section-title;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
&__greeting {
font-size: var(--tk-font-h2);
color: $tx2;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
&__date {
@@ -29,8 +29,8 @@
&__alert {
display: flex;
align-items: center;
margin: 16px 24px;
padding: 16px 20px;
margin: var(--tk-gap-md) var(--tk-page-padding);
padding: var(--tk-gap-md) var(--tk-section-gap);
background: $dan-l;
border-radius: $r;
border-left: 4px solid $dan;
@@ -46,7 +46,7 @@
line-height: 36px;
font-weight: bold;
font-size: var(--tk-font-body);
margin-right: 12px;
margin-right: var(--tk-gap-sm);
flex-shrink: 0;
}
@@ -63,19 +63,19 @@
}
&__search {
margin: 0 24px 16px;
margin: 0 var(--tk-page-padding) var(--tk-gap-md);
}
&__search-input {
background: $surface-alt;
border-radius: $r;
padding: 16px 20px;
padding: var(--tk-gap-md) var(--tk-section-gap);
font-size: var(--tk-font-h1);
color: $tx3;
}
&__section {
margin-bottom: 40px;
margin-bottom: var(--tk-gap-2xl);
}
&__section-title {
@@ -85,19 +85,19 @@
&__grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
gap: var(--tk-section-gap);
}
&__card {
background: $card;
border-radius: $r-lg;
padding: 28px 24px;
padding: var(--tk-card-padding-lg) var(--tk-card-padding);
text-align: center;
box-shadow: $shadow-md;
transition: transform 0.15s;
&:active {
transform: scale(0.97);
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -107,12 +107,12 @@
width: 56px;
height: 56px;
border-radius: $r;
background: $pri-l;
color: $pri;
background: var(--tk-pri-l);
color: var(--tk-pri);
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-body-lg);
font-weight: 700;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
&__card-num {
@@ -121,7 +121,7 @@
font-weight: 700;
color: $tx;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
&__card-label {
@@ -132,7 +132,7 @@
&__quick-actions {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
gap: var(--tk-section-gap);
}
&__footer {
@@ -144,7 +144,7 @@
&__logout {
color: $dan;
font-size: var(--tk-font-h2);
padding: 16px 48px;
padding: var(--tk-gap-md) var(--tk-gap-2xl);
display: inline-block;
}
}
@@ -153,12 +153,12 @@
flex: 1;
background: $card;
border-radius: $r-lg;
padding: 28px 20px;
padding: var(--tk-card-padding-lg) var(--tk-section-gap);
text-align: center;
box-shadow: $shadow-md;
&:active {
opacity: 0.8;
opacity: var(--tk-touch-feedback-opacity);
}
&__initial {
@@ -177,7 +177,7 @@
&__icon-wrap {
position: relative;
display: inline-flex;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
&__badge {

View File

@@ -2,7 +2,7 @@ import { useState, useMemo, useCallback } from 'react';
import { View, Text, Input } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useAuthStore } from '@/stores/auth';
import { useElderClass } from '../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import { usePageData } from '@/hooks/usePageData';
import { getDashboard, type DoctorDashboard } from '@/services/doctor/dashboard';
import Loading from '@/components/Loading';
@@ -58,7 +58,7 @@ export default function DoctorHome() {
const user = useAuthStore((s) => s.user);
const logout = useAuthStore((s) => s.logout);
const roles = useAuthStore((s) => s.roles);
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [dashboard, setDashboard] = useState<DoctorDashboard | null>(null);
const [alertCount, setAlertCount] = useState(0);
const [loading, setLoading] = useState(true);

View File

@@ -8,13 +8,13 @@
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
gap: var(--tk-gap-md);
}
.info-item {
display: flex;
flex-direction: column;
gap: 4px;
gap: var(--tk-gap-2xs);
}
.info-label {
@@ -31,8 +31,8 @@
.warning-card {
background: $wrn-l;
border-radius: $r;
padding: 20px;
margin-bottom: 16px;
padding: var(--tk-section-gap);
margin-bottom: var(--tk-gap-md);
}
.warning-label {
@@ -40,23 +40,23 @@
color: $wrn;
font-weight: 600;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.warning-text {
font-size: var(--tk-font-h1);
color: $pri-d;
color: var(--tk-pri-d);
}
.info-block {
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.info-block-label {
font-size: var(--tk-font-body);
color: $tx3;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.info-block-text {
@@ -68,14 +68,14 @@
.vitals-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 16px;
gap: var(--tk-gap-md);
margin-bottom: var(--tk-gap-md);
}
.vital-item {
background: $pri-l;
background: var(--tk-pri-l);
border-radius: $r;
padding: 20px;
padding: var(--tk-section-gap);
text-align: center;
}
@@ -83,7 +83,7 @@
@include serif-number;
font-size: var(--tk-font-num-lg);
font-weight: 700;
color: $pri;
color: var(--tk-pri);
display: block;
margin-bottom: 4px;
}
@@ -97,7 +97,7 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 0;
padding: var(--tk-gap-md) 0;
}
.stat-label {
@@ -117,7 +117,7 @@
}
.lab-item {
padding: 20px 0;
padding: var(--tk-section-gap) 0;
border-bottom: 1px solid $bd-l;
&:last-child {
@@ -125,14 +125,14 @@
}
&:active {
background: $bd-l;
opacity: var(--tk-touch-feedback-opacity);
}
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
&__type {
@@ -155,21 +155,21 @@
.action-buttons {
display: flex;
gap: 16px;
gap: var(--tk-gap-md);
}
.action-btn {
flex: 1;
text-align: center;
padding: 20px;
padding: var(--tk-section-gap);
border-radius: $r;
background: $pri;
background: var(--tk-pri);
color: $card;
font-size: var(--tk-font-h1);
font-weight: 500;
&:active {
opacity: 0.85;
opacity: var(--tk-touch-feedback-opacity);
}
text {
@@ -179,7 +179,7 @@
.error-text {
text-align: center;
padding: 80px 32px;
padding: 80px var(--tk-gap-xl);
color: $tx3;
font-size: var(--tk-font-body-lg);
}

View File

@@ -6,13 +6,13 @@ import { getPatient, getHealthSummary, type PatientDetail, type HealthSummary }
import Loading from '@/components/Loading';
import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import { useElderClass } from '../../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import './index.scss';
export default function PatientDetail() {
const router = useRouter();
const patientId = router.params.id || '';
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [patient, setPatient] = useState<PatientDetail | null>(null);
const [summary, setSummary] = useState<HealthSummary | null>(null);
const [loading, setLoading] = useState(true);

View File

@@ -6,7 +6,7 @@
// StatusTag 已接管patient-card__status 标签样式
.patient-count {
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
text {
font-size: var(--tk-font-h2);
@@ -23,7 +23,7 @@
.patient-card__header {
display: flex;
align-items: center;
gap: 16px;
gap: var(--tk-gap-md);
}
.patient-card__name {
@@ -41,12 +41,12 @@
.patient-card__tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
gap: var(--tk-gap-xs);
margin-top: var(--tk-gap-sm);
}
.patient-tag {
padding: 4px 14px;
padding: var(--tk-gap-2xs) 14px;
border-radius: $r;
background: rgba($pri, 0.1);
@@ -57,7 +57,7 @@
.load-more-hint-wrap {
text-align: center;
padding: 20px;
padding: var(--tk-section-gap);
}
.load-more-hint {

View File

@@ -10,11 +10,11 @@ import LoadingCard from '@/components/ui/LoadingCard';
import SearchSection from '@/components/patterns/SearchSection';
import EmptyState from '@/components/EmptyState';
import Loading from '@/components/Loading';
import { useElderClass } from '../../../hooks/useElderClass';
import { useDoctorClass } from '@/hooks/useDoctorClass';
import './index.scss';
export default function PatientList() {
const modeClass = useElderClass();
const modeClass = useDoctorClass();
const [patients, setPatients] = useState<PatientItem[]>([]);
const [tags, setTags] = useState<PatientTag[]>([]);
const [activeTag, setActiveTag] = useState<string>('');

View File

@@ -19,7 +19,7 @@
}
.alerts-tab.active {
background: $pri;
background: var(--tk-pri);
}
.alerts-tab-text {

View File

@@ -6,7 +6,7 @@
/* ── hero ── */
.dm-hero {
padding: 48px 32px 36px;
padding: var(--tk-gap-2xl) var(--tk-gap-xl) 36px;
display: flex;
flex-direction: column;
align-items: center;
@@ -17,21 +17,21 @@
width: 88px;
height: 88px;
border-radius: $r-lg;
background: $pri-l;
margin-bottom: 20px;
background: var(--tk-pri-l);
margin-bottom: var(--tk-section-gap);
}
.dm-hero-icon-text {
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-hero);
font-weight: bold;
color: $pri;
color: var(--tk-pri);
}
.dm-hero-title {
@include section-title;
font-size: var(--tk-font-num-lg);
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.dm-hero-sub {
@@ -41,14 +41,14 @@
/* ── card (standalone, used for date picker) ── */
.dm-card {
margin: 0 24px 20px;
margin: 0 var(--tk-gap-lg) var(--tk-section-gap);
}
.dm-card-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
margin-bottom: var(--tk-section-gap);
}
.dm-card-title {
@@ -71,12 +71,12 @@
align-items: center;
background: $bg;
border-radius: $r-sm;
padding: 22px 24px;
padding: 22px var(--tk-gap-lg);
}
.dm-date-value {
font-size: var(--tk-font-body-lg);
color: $pri;
color: var(--tk-pri);
@include serif-number;
font-weight: bold;
}
@@ -91,7 +91,7 @@
/* ── collapsible group ── */
.dm-group {
margin: 0 24px 20px;
margin: 0 var(--tk-gap-lg) var(--tk-section-gap);
overflow: hidden;
}
@@ -99,10 +99,10 @@
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px 28px;
padding: var(--tk-gap-lg) var(--tk-card-padding-lg);
&:active {
background: $bd-l;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -125,7 +125,7 @@
}
.dm-group-body {
padding: 0 28px 28px;
padding: 0 var(--tk-card-padding-lg) var(--tk-card-padding-lg);
}
.dm-group-collapsed .dm-group-body {
@@ -134,7 +134,7 @@
/* ── inner field spacing (within groups) ── */
.dm-inner-field {
margin-bottom: 24px;
margin-bottom: var(--tk-gap-lg);
&:last-child {
margin-bottom: 0;
@@ -145,7 +145,7 @@
.dm-bp-group {
display: flex;
align-items: flex-end;
gap: 12px;
gap: var(--tk-gap-sm);
}
.dm-bp-field {
@@ -156,14 +156,14 @@
font-size: var(--tk-font-body);
color: $tx2;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.dm-bp-divider {
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 20px;
padding-bottom: var(--tk-section-gap);
gap: 6px;
}
@@ -192,7 +192,7 @@
.dm-single-row {
display: flex;
align-items: center;
gap: 16px;
gap: var(--tk-gap-md);
}
.dm-input-flex {
@@ -210,7 +210,7 @@
.dm-input-box {
background: $bg;
border-radius: $r-sm;
padding: 20px 24px;
padding: var(--tk-section-gap) var(--tk-gap-lg);
font-size: var(--tk-font-body-lg);
color: $tx;
@include serif-number;
@@ -230,7 +230,7 @@
.dm-field-warning {
font-size: var(--tk-font-body);
color: $wrn;
margin-top: 8px;
margin-top: var(--tk-gap-xs);
display: block;
}
@@ -240,16 +240,16 @@
/* ── submit ── */
.dm-submit {
background: $pri;
background: var(--tk-pri);
border-radius: $r;
padding: 26px;
text-align: center;
margin: 40px 24px 0;
margin: 40px var(--tk-gap-lg) 0;
box-shadow: $shadow-md;
transition: opacity 0.2s;
&:active {
opacity: 0.85;
opacity: var(--tk-touch-feedback-opacity);
}
}
@@ -268,8 +268,8 @@
/* ── reset ── */
.dm-reset {
text-align: center;
padding: 24px;
margin-top: 12px;
padding: var(--tk-gap-lg);
margin-top: var(--tk-gap-sm);
}
.dm-reset-text {

View File

@@ -5,8 +5,8 @@
// ContentCard 已接管sync-status-card/sync-result-card 背景/圆角/阴影
.sync-header {
background: $pri;
padding: 48px 32px 32px;
background: var(--tk-pri);
padding: var(--tk-gap-2xl) var(--tk-gap-xl) var(--tk-gap-xl);
color: $card;
}
@@ -16,17 +16,17 @@
}
.sync-section {
padding: 24px;
padding: var(--tk-gap-lg);
}
.sync-hero {
display: flex;
flex-direction: column;
align-items: center;
padding: 48px 24px;
padding: var(--tk-gap-2xl) var(--tk-gap-lg);
background: $card;
border-radius: $r;
margin-bottom: 24px;
margin-bottom: var(--tk-gap-lg);
box-shadow: $shadow-sm;
}
@@ -34,10 +34,10 @@
width: 80px;
height: 80px;
border-radius: 50%;
background: $pri-l;
background: var(--tk-pri-l);
@include flex-center;
margin-bottom: 20px;
color: $pri;
margin-bottom: var(--tk-section-gap);
color: var(--tk-pri);
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-num-lg);
font-weight: bold;
@@ -45,7 +45,7 @@
.sync-hero-title {
@include section-title;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.sync-hero-desc {
@@ -55,20 +55,20 @@
.sync-action {
@include flex-center;
background: $pri;
background: var(--tk-pri);
border-radius: $r-sm;
padding: 20px 40px;
margin: 12px 0;
padding: var(--tk-section-gap) var(--tk-gap-2xl);
margin: var(--tk-gap-sm) 0;
&--primary {
flex: 1;
background: $pri;
background: var(--tk-pri);
}
&--danger {
flex: 1;
background: $dan;
margin-left: 16px;
margin-left: var(--tk-gap-md);
}
}
@@ -79,7 +79,7 @@
}
.sync-device-list {
margin-top: 16px;
margin-top: var(--tk-gap-md);
}
.sync-section-title {
@@ -87,7 +87,7 @@
font-size: var(--tk-font-body-lg);
font-weight: bold;
color: $tx;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
display: block;
}
@@ -97,8 +97,8 @@
align-items: center;
background: $card;
border-radius: $r-sm;
padding: 24px;
margin-bottom: 12px;
padding: var(--tk-gap-lg);
margin-bottom: var(--tk-gap-sm);
box-shadow: $shadow-sm;
}
@@ -127,14 +127,14 @@
.sync-status-card {
display: flex;
align-items: center;
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
.sync-status-dot {
width: 16px;
height: 16px;
border-radius: 50%;
margin-right: 16px;
margin-right: var(--tk-gap-md);
background: $tx3;
&--connected {
@@ -150,8 +150,8 @@
.sync-readings-panel {
background: $card;
border-radius: $r-sm;
padding: 24px;
margin-bottom: 16px;
padding: var(--tk-gap-lg);
margin-bottom: var(--tk-gap-md);
box-shadow: $shadow-sm;
}
@@ -159,7 +159,7 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
padding: var(--tk-gap-sm) 0;
border-bottom: 1px solid $bd-l;
&:last-child {
@@ -175,13 +175,13 @@
.sync-reading-value {
font-size: var(--tk-font-body-lg);
font-weight: bold;
color: $pri;
color: var(--tk-pri);
@include serif-number;
}
.sync-readings-count {
display: block;
margin-top: 12px;
margin-top: var(--tk-gap-sm);
font-size: var(--tk-font-body);
color: var(--tk-text-secondary);
text-align: center;
@@ -189,12 +189,12 @@
.sync-actions-row {
display: flex;
gap: 12px;
gap: var(--tk-gap-sm);
}
.sync-error {
margin: 24px;
padding: 20px 24px;
margin: var(--tk-gap-lg);
padding: var(--tk-section-gap) var(--tk-gap-lg);
background: $dan-l;
border-radius: $r-sm;
}
@@ -218,7 +218,7 @@
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 24px;
margin-bottom: var(--tk-gap-lg);
box-shadow: $shadow-sm;
}
@@ -232,12 +232,12 @@
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-num-lg);
font-weight: bold;
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
}
.sync-result-title {
@include section-title;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.sync-result-count {

View File

@@ -6,7 +6,7 @@
/* ── hero ── */
.input-hero {
padding: 48px 32px 36px;
padding: var(--tk-gap-2xl) var(--tk-gap-xl) 36px;
display: flex;
flex-direction: column;
align-items: center;
@@ -17,21 +17,21 @@
width: 88px;
height: 88px;
border-radius: $r-lg;
background: $pri-l;
margin-bottom: 20px;
background: var(--tk-pri-l);
margin-bottom: var(--tk-section-gap);
}
.input-hero-icon-text {
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-hero);
font-weight: bold;
color: $pri;
color: var(--tk-pri);
}
.input-hero-title {
@include section-title;
font-size: var(--tk-font-num-lg);
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.input-hero-sub {
@@ -44,18 +44,18 @@
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 24px 20px;
border: 1px dashed $pri;
margin: 0 var(--tk-gap-lg) var(--tk-section-gap);
border: 1px dashed var(--tk-pri);
&:active {
opacity: 0.7;
opacity: var(--tk-touch-feedback-opacity);
}
}
.input-sync-entry-text {
font-size: var(--tk-font-body-lg);
font-weight: 600;
color: $pri;
color: var(--tk-pri);
}
.input-sync-entry-hint {
@@ -65,14 +65,14 @@
/* ── card ── */
.input-card {
margin: 0 24px 20px;
margin: 0 var(--tk-gap-lg) var(--tk-section-gap);
}
.input-card-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
margin-bottom: var(--tk-section-gap);
}
.input-card-indicator {
@@ -104,7 +104,7 @@
align-items: center;
background: $bg;
border-radius: $r-sm;
padding: 22px 24px;
padding: 22px var(--tk-gap-lg);
}
.input-picker-value {
@@ -127,7 +127,7 @@
font-size: var(--tk-font-body-lg);
font-weight: bold;
color: $tx;
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
display: block;
}
@@ -135,7 +135,7 @@
.input-bp-group {
display: flex;
align-items: flex-end;
gap: 12px;
gap: var(--tk-gap-sm);
}
.input-bp-field {
@@ -146,14 +146,14 @@
font-size: var(--tk-font-body);
color: $tx2;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.input-bp-divider {
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 20px;
padding-bottom: var(--tk-section-gap);
gap: 6px;
}
@@ -174,7 +174,7 @@
.input-field-box {
background: $bg;
border-radius: $r-sm;
padding: 20px 24px;
padding: var(--tk-section-gap) var(--tk-gap-lg);
font-size: var(--tk-font-body-lg);
color: $tx;
@include serif-number;
@@ -195,16 +195,16 @@
/* ── submit ── */
.input-submit {
background: $pri;
background: var(--tk-pri);
border-radius: $r;
padding: 26px;
text-align: center;
margin: 48px 24px 0;
margin: var(--tk-gap-2xl) var(--tk-gap-lg) 0;
box-shadow: $shadow-md;
transition: opacity 0.2s;
&:active {
opacity: 0.85;
opacity: var(--tk-touch-feedback-opacity);
}
}

View File

@@ -21,7 +21,7 @@
width: 88px;
height: 88px;
border-radius: $r-lg;
background: $pri-l;
background: var(--tk-pri-l);
margin-bottom: 20px;
}
@@ -29,7 +29,7 @@
font-family: 'Georgia', 'Times New Roman', serif;
font-size: var(--tk-font-hero);
font-weight: bold;
color: $pri;
color: var(--tk-pri);
}
.trend-hero-title {
@@ -131,7 +131,7 @@
.trend-item-value {
font-size: var(--tk-font-body-lg);
color: $pri;
color: var(--tk-pri);
@include serif-number;
font-weight: bold;
}

View File

@@ -6,9 +6,9 @@
/* ===== 余额卡片 ===== */
.balance-card {
background: $card;
margin: 20px 24px 16px;
margin: var(--tk-section-gap) var(--tk-page-padding) var(--tk-gap-md);
border-radius: $r-lg;
padding: 32px;
padding: var(--tk-gap-xl);
box-shadow: $shadow-sm;
}
@@ -16,16 +16,16 @@
font-size: var(--tk-font-h2);
color: $tx2;
display: block;
margin-bottom: 8px;
margin-bottom: var(--tk-gap-xs);
}
.balance-value {
@include serif-number;
font-size: var(--tk-font-hero);
font-weight: bold;
color: $pri;
color: var(--tk-pri);
display: block;
margin-bottom: 28px;
margin-bottom: var(--tk-card-padding-lg);
letter-spacing: -1px;
}
@@ -34,7 +34,7 @@
align-items: center;
background: $bg;
border-radius: $r;
padding: 20px 0;
padding: var(--tk-section-gap) 0;
}
.stat-item {
@@ -48,7 +48,7 @@
@include serif-number;
font-size: var(--tk-font-num);
font-weight: bold;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
&.stat-earn {
color: $acc;
@@ -78,14 +78,14 @@
.type-tabs {
display: flex;
gap: 0;
padding: 0 24px;
margin-bottom: 16px;
padding: 0 var(--tk-page-padding);
margin-bottom: var(--tk-gap-md);
}
.type-tab {
@include flex-center;
flex: 1;
padding: 16px 0;
padding: var(--tk-gap-md) 0;
position: relative;
&.active::after {
@@ -96,7 +96,7 @@
transform: translateX(-50%);
width: 40px;
height: 4px;
background: $pri;
background: var(--tk-pri);
border-radius: $r-xs;
}
}
@@ -106,14 +106,14 @@
color: $tx3;
.type-tab.active & {
color: $pri;
color: var(--tk-pri);
font-weight: bold;
}
}
/* ===== 交易列表 ===== */
.transaction-list {
padding: 0 24px;
padding: 0 var(--tk-page-padding);
}
.transaction-item {
@@ -121,8 +121,8 @@
align-items: center;
background: $card;
border-radius: $r;
padding: 24px;
margin-bottom: 12px;
padding: var(--tk-card-padding);
margin-bottom: var(--tk-gap-sm);
box-shadow: $shadow-sm;
}
@@ -131,7 +131,7 @@
height: 64px;
border-radius: $r;
@include flex-center;
margin-right: 20px;
margin-right: var(--tk-section-gap);
flex-shrink: 0;
&.tx-badge-earn {
@@ -174,7 +174,7 @@
font-size: var(--tk-font-body-lg);
color: $tx;
display: block;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -190,7 +190,7 @@
display: flex;
flex-direction: column;
align-items: flex-end;
margin-left: 16px;
margin-left: var(--tk-gap-md);
flex-shrink: 0;
}
@@ -198,7 +198,7 @@
@include serif-number;
font-size: var(--tk-font-num);
font-weight: bold;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
&.tx-amount-positive {
color: $acc;

View File

@@ -10,9 +10,9 @@
.product-card {
display: flex;
align-items: center;
padding: 32px 24px;
padding: var(--tk-gap-xl) var(--tk-gap-lg);
background: $card;
margin: 20px 24px 16px;
margin: var(--tk-section-gap) var(--tk-gap-lg) var(--tk-gap-md);
border-radius: $r-lg;
box-shadow: $shadow-sm;
}
@@ -22,7 +22,7 @@
height: 128px;
border-radius: $r;
@include flex-center;
margin-right: 24px;
margin-right: var(--tk-gap-lg);
flex-shrink: 0;
&--physical {
@@ -30,11 +30,11 @@
}
&--service {
background: $pri;
background: var(--tk-pri);
}
&--privilege {
background: $pri-d;
background: var(--tk-pri-d);
}
}
@@ -55,20 +55,20 @@
font-weight: bold;
color: $tx;
display: block;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.product-type-tag {
@include tag($pri-l, $pri-d);
@include tag(var(--tk-pri-l), var(--tk-pri-d));
}
/* ===== 兑换明细 ===== */
.detail-section {
padding: 0 24px;
margin-bottom: 16px;
padding: 0 var(--tk-gap-lg);
margin-bottom: var(--tk-gap-md);
}
.detail-section-title {
@@ -79,14 +79,14 @@
background: $card;
border-radius: $r;
box-shadow: $shadow-sm;
padding: 0 24px;
padding: 0 var(--tk-gap-lg);
}
.detail-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 0;
padding: var(--tk-gap-lg) 0;
border-bottom: 1px solid $bd-l;
&.last {
@@ -106,7 +106,7 @@
font-weight: bold;
&.detail-cost {
color: $pri;
color: var(--tk-pri);
font-size: var(--tk-font-num-lg);
}
@@ -122,8 +122,8 @@
/* ===== 温馨提示 ===== */
.notice-section {
background: $card;
padding: 24px;
margin: 0 24px;
padding: var(--tk-gap-lg);
margin: 0 var(--tk-gap-lg);
border-radius: $r;
box-shadow: $shadow-sm;
}
@@ -131,7 +131,7 @@
.notice-title {
@include section-title;
font-size: var(--tk-font-body-lg);
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.notice-text {
@@ -139,7 +139,7 @@
color: $tx3;
display: block;
line-height: 1.7;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
}
/* ===== 底部操作栏 ===== */
@@ -150,10 +150,10 @@
right: 0;
display: flex;
align-items: center;
padding: 16px 24px;
padding-bottom: calc(16px + env(safe-area-inset-bottom));
padding: var(--tk-gap-md) var(--tk-gap-lg);
padding-bottom: calc(var(--tk-gap-md) + env(safe-area-inset-bottom));
background: $card;
box-shadow: 0 -2px 12px rgba(45, 42, 38, 0.06);
box-shadow: 0 -2px 12px rgba($tx, 0.06);
z-index: 10;
}
@@ -172,18 +172,18 @@
@include serif-number;
font-size: var(--tk-font-num-lg);
font-weight: bold;
color: $pri;
color: var(--tk-pri);
}
.footer-cost-unit {
font-size: var(--tk-font-body);
color: $tx2;
margin-left: 4px;
margin-left: var(--tk-gap-2xs);
}
.confirm-btn {
background: $pri;
padding: 20px 48px;
background: var(--tk-pri);
padding: var(--tk-section-gap) var(--tk-gap-2xl);
border-radius: $r-pill;
transition: opacity 0.2s;

View File

@@ -6,11 +6,11 @@
/* ===== 订单列表 ===== */
.order-list {
padding: 0 24px;
padding: 0 var(--tk-gap-lg);
}
.order-card {
margin-bottom: 16px;
margin-bottom: var(--tk-gap-md);
overflow: hidden;
}
@@ -18,7 +18,7 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 24px 16px;
padding: var(--tk-gap-lg) var(--tk-gap-lg) var(--tk-gap-md);
border-bottom: 1px solid $bd-l;
}
@@ -34,9 +34,9 @@
}
.order-status-tag {
padding: 4px 16px;
padding: var(--tk-gap-2xs) var(--tk-gap-md);
border-radius: $r-pill;
margin-left: 12px;
margin-left: var(--tk-gap-sm);
flex-shrink: 0;
&--pending {
@@ -62,14 +62,14 @@
}
.order-body {
padding: 16px 24px 20px;
padding: var(--tk-gap-md) var(--tk-gap-lg) var(--tk-section-gap);
}
.order-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
padding: var(--tk-gap-xs) 0;
}
.order-row-label {
@@ -83,7 +83,7 @@
color: $tx;
&.order-cost {
color: $pri;
color: var(--tk-pri);
font-weight: bold;
}
}
@@ -92,22 +92,22 @@
.order-qrcode {
display: flex;
align-items: center;
padding: 16px;
margin-top: 12px;
background: $pri-l;
padding: var(--tk-gap-md);
margin-top: var(--tk-gap-sm);
background: var(--tk-pri-l);
border-radius: $r-sm;
}
.qrcode-label {
font-size: var(--tk-font-h2);
color: $tx3;
margin-right: 8px;
margin-right: var(--tk-gap-xs);
}
.qrcode-value {
@include serif-number;
font-size: var(--tk-font-h2);
color: $pri-d;
color: var(--tk-pri-d);
font-weight: bold;
flex: 1;
overflow: hidden;
@@ -117,7 +117,7 @@
.qrcode-tap {
font-size: var(--tk-font-body);
color: $pri;
margin-left: 8px;
color: var(--tk-pri);
margin-left: var(--tk-gap-xs);
flex-shrink: 0;
}

View File

@@ -5,19 +5,19 @@
.page-title {
@include section-title;
padding-left: 4px;
padding-left: var(--tk-gap-2xs);
}
.consent-list {
display: flex;
flex-direction: column;
gap: 16px;
gap: var(--tk-gap-md);
}
.consent-card {
background: $card;
border-radius: $r;
padding: 28px;
padding: var(--tk-card-padding-lg);
box-shadow: $shadow-sm;
}
@@ -25,7 +25,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
margin-bottom: var(--tk-gap-sm);
}
.consent-card__type {
@@ -52,19 +52,19 @@
font-size: var(--tk-font-h2);
color: $tx2;
display: block;
margin-bottom: 4px;
margin-bottom: var(--tk-gap-2xs);
font-variant-numeric: tabular-nums;
}
.revoke-btn {
margin-top: 16px;
padding: 12px 0;
margin-top: var(--tk-gap-md);
padding: var(--tk-gap-sm) 0;
text-align: center;
border-radius: $r-sm;
border: 1px solid $dan;
&:active {
background: $dan-l;
opacity: var(--tk-touch-feedback-opacity);
}
&--disabled {

Some files were not shown because too many files have changed in this diff Show More