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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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