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:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
52
apps/miniprogram/src/components/ui/AlertCard/index.scss
Normal file
52
apps/miniprogram/src/components/ui/AlertCard/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
41
apps/miniprogram/src/components/ui/AlertCard/index.tsx
Normal file
41
apps/miniprogram/src/components/ui/AlertCard/index.tsx
Normal 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);
|
||||
37
apps/miniprogram/src/components/ui/ChatBubble/index.scss
Normal file
37
apps/miniprogram/src/components/ui/ChatBubble/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
34
apps/miniprogram/src/components/ui/ChatBubble/index.tsx
Normal file
34
apps/miniprogram/src/components/ui/ChatBubble/index.tsx
Normal 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);
|
||||
@@ -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> = ({
|
||||
|
||||
51
apps/miniprogram/src/components/ui/FormInput/index.scss
Normal file
51
apps/miniprogram/src/components/ui/FormInput/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
55
apps/miniprogram/src/components/ui/FormInput/index.tsx
Normal file
55
apps/miniprogram/src/components/ui/FormInput/index.tsx
Normal 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);
|
||||
@@ -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;
|
||||
}
|
||||
21
apps/miniprogram/src/components/ui/GradientHeader/index.tsx
Normal file
21
apps/miniprogram/src/components/ui/GradientHeader/index.tsx
Normal 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);
|
||||
27
apps/miniprogram/src/components/ui/InfoRow/index.scss
Normal file
27
apps/miniprogram/src/components/ui/InfoRow/index.scss
Normal 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);
|
||||
}
|
||||
}
|
||||
34
apps/miniprogram/src/components/ui/InfoRow/index.tsx
Normal file
34
apps/miniprogram/src/components/ui/InfoRow/index.tsx
Normal 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);
|
||||
67
apps/miniprogram/src/components/ui/ListItem/index.scss
Normal file
67
apps/miniprogram/src/components/ui/ListItem/index.scss
Normal 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);
|
||||
}
|
||||
}
|
||||
46
apps/miniprogram/src/components/ui/ListItem/index.tsx
Normal file
46
apps/miniprogram/src/components/ui/ListItem/index.tsx
Normal 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);
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> = ({
|
||||
|
||||
54
apps/miniprogram/src/components/ui/PrimaryButton/index.scss
Normal file
54
apps/miniprogram/src/components/ui/PrimaryButton/index.scss
Normal 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); }
|
||||
}
|
||||
38
apps/miniprogram/src/components/ui/PrimaryButton/index.tsx
Normal file
38
apps/miniprogram/src/components/ui/PrimaryButton/index.tsx
Normal 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);
|
||||
28
apps/miniprogram/src/components/ui/ProgressRing/index.scss
Normal file
28
apps/miniprogram/src/components/ui/ProgressRing/index.scss
Normal 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);
|
||||
}
|
||||
}
|
||||
56
apps/miniprogram/src/components/ui/ProgressRing/index.tsx
Normal file
56
apps/miniprogram/src/components/ui/ProgressRing/index.tsx
Normal 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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
31
apps/miniprogram/src/components/ui/SecondaryButton/index.tsx
Normal file
31
apps/miniprogram/src/components/ui/SecondaryButton/index.tsx
Normal 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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
87
apps/miniprogram/src/components/ui/TabFilter/index.scss
Normal file
87
apps/miniprogram/src/components/ui/TabFilter/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
43
apps/miniprogram/src/components/ui/TabFilter/index.tsx
Normal file
43
apps/miniprogram/src/components/ui/TabFilter/index.tsx
Normal 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);
|
||||
41
apps/miniprogram/src/components/ui/VitalCard/index.scss
Normal file
41
apps/miniprogram/src/components/ui/VitalCard/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
41
apps/miniprogram/src/components/ui/VitalCard/index.tsx
Normal file
41
apps/miniprogram/src/components/ui/VitalCard/index.tsx
Normal 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);
|
||||
6
apps/miniprogram/src/hooks/useDoctorClass.ts
Normal file
6
apps/miniprogram/src/hooks/useDoctorClass.ts
Normal 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(' ');
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/* ─── 未读角标(页面特有) ─── */
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'>✓</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">✓</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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('');
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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('');
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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('');
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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[]>([]);
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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('');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>('');
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
}
|
||||
|
||||
.alerts-tab.active {
|
||||
background: $pri;
|
||||
background: var(--tk-pri);
|
||||
}
|
||||
|
||||
.alerts-tab-text {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user