feat(miniprogram): 温润东方风全面 UI 重设计
73 文件变更,覆盖全部 40 个页面 SCSS + TabBar 图标 + 组件样式。 统一赤陶主色 #C4623A + 暖米背景 + 衬线标题字体 + 12px 圆角体系。
@@ -43,8 +43,8 @@ export default defineAppConfig({
|
||||
'pages/device-sync/index',
|
||||
],
|
||||
tabBar: {
|
||||
color: '#94A3B8',
|
||||
selectedColor: '#0891B2',
|
||||
color: '#A8A29E',
|
||||
selectedColor: '#C4623A',
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderStyle: 'white',
|
||||
list: [
|
||||
@@ -56,9 +56,9 @@ export default defineAppConfig({
|
||||
],
|
||||
},
|
||||
window: {
|
||||
backgroundTextStyle: 'light',
|
||||
navigationBarBackgroundColor: '#0891B2',
|
||||
backgroundTextStyle: 'dark',
|
||||
navigationBarBackgroundColor: '#FFFFFF',
|
||||
navigationBarTitleText: '健康管理',
|
||||
navigationBarTextStyle: 'white',
|
||||
navigationBarTextStyle: 'black',
|
||||
},
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 334 B |
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 334 B |
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 334 B |
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 334 B |
|
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 334 B |
98
apps/miniprogram/src/components/EcCanvas/index.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import React, { useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
|
||||
import { Canvas, View } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { LineChart } from 'echarts/charts';
|
||||
import {
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
MarkAreaComponent,
|
||||
MarkPointComponent,
|
||||
} from 'echarts/components';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
|
||||
echarts.use([
|
||||
LineChart,
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
MarkAreaComponent,
|
||||
MarkPointComponent,
|
||||
CanvasRenderer,
|
||||
]);
|
||||
|
||||
interface EcCanvasProps {
|
||||
canvasId: string;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export interface EcCanvasRef {
|
||||
setOption: (option: echarts.EChartsOption) => void;
|
||||
}
|
||||
|
||||
const EcCanvas = forwardRef<EcCanvasRef, EcCanvasProps>(
|
||||
({ canvasId, height = 300 }, ref) => {
|
||||
const chartInstance = useRef<echarts.ECharts | null>(null);
|
||||
const canvasNode = useRef<any>(null);
|
||||
|
||||
const initChart = async () => {
|
||||
try {
|
||||
const query = Taro.createSelectorQuery();
|
||||
query
|
||||
.select(`#${canvasId}`)
|
||||
.node()
|
||||
.exec((res) => {
|
||||
const node = res[0]?.node;
|
||||
if (!node) return;
|
||||
|
||||
canvasNode.current = node;
|
||||
const dpr = Taro.getSystemInfoSync().pixelRatio;
|
||||
const width = node.width || 350;
|
||||
const heightVal = node.height || height;
|
||||
|
||||
node.width = width * dpr;
|
||||
node.height = heightVal * dpr;
|
||||
|
||||
const ctx = node.getContext('2d');
|
||||
|
||||
chartInstance.current = echarts.init(ctx as any, undefined, {
|
||||
renderer: 'canvas',
|
||||
width,
|
||||
height: heightVal,
|
||||
devicePixelRatio: dpr,
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('EcCanvas init failed:', e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initChart();
|
||||
return () => {
|
||||
chartInstance.current?.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
setOption: (option: echarts.EChartsOption) => {
|
||||
if (chartInstance.current) {
|
||||
chartInstance.current.setOption(option);
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<View style={{ width: '100%', height: `${height}rpx` }}>
|
||||
<Canvas
|
||||
type='2d'
|
||||
id={canvasId}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
EcCanvas.displayName = 'EcCanvas';
|
||||
|
||||
export default EcCanvas;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useRef, useCallback } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { EChart } from 'echarts-taro3-react';
|
||||
import EcCanvas from '../EcCanvas';
|
||||
import type { EcCanvasRef } from '../EcCanvas';
|
||||
import './index.scss';
|
||||
|
||||
interface TrendChartProps {
|
||||
@@ -11,8 +12,14 @@ interface TrendChartProps {
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export default function TrendChart({ data, referenceMin, referenceMax, unit = '', height = 500 }: TrendChartProps) {
|
||||
const chartRef = useRef<any>(null);
|
||||
export default function TrendChart({
|
||||
data,
|
||||
referenceMin,
|
||||
referenceMax,
|
||||
unit = '',
|
||||
height = 500,
|
||||
}: TrendChartProps) {
|
||||
const chartRef = useRef<EcCanvasRef>(null);
|
||||
|
||||
const getOption = useCallback(() => {
|
||||
if (!data || data.length === 0) return null;
|
||||
@@ -21,10 +28,15 @@ export default function TrendChart({ data, referenceMin, referenceMax, unit = ''
|
||||
const markArea: any = {};
|
||||
|
||||
if (referenceMin != null && referenceMax != null) {
|
||||
markArea.data = [[
|
||||
{ yAxis: referenceMin, itemStyle: { color: 'rgba(5,150,105,0.08)' } },
|
||||
{ yAxis: referenceMax },
|
||||
]];
|
||||
markArea.data = [
|
||||
[
|
||||
{
|
||||
yAxis: referenceMin,
|
||||
itemStyle: { color: 'rgba(5,150,105,0.08)' },
|
||||
},
|
||||
{ yAxis: referenceMax },
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
series.push({
|
||||
@@ -35,17 +47,34 @@ export default function TrendChart({ data, referenceMin, referenceMax, unit = ''
|
||||
symbolSize: 6,
|
||||
lineStyle: { color: '#0891B2', width: 2 },
|
||||
itemStyle: { color: '#0891B2' },
|
||||
areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: 'rgba(8,145,178,0.15)' }, { offset: 1, color: 'rgba(8,145,178,0.01)' }] } },
|
||||
markArea: markArea.data ? { silent: true, data: markArea.data } : undefined,
|
||||
markPoint: (referenceMin != null && referenceMax != null) ? {
|
||||
data: data
|
||||
.filter((d) => d.value < referenceMin || d.value > referenceMax)
|
||||
.map((d) => ({
|
||||
coord: [data.indexOf(d), d.value],
|
||||
itemStyle: { color: '#DC2626' },
|
||||
symbolSize: 12,
|
||||
})),
|
||||
} : undefined,
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(8,145,178,0.15)' },
|
||||
{ offset: 1, color: 'rgba(8,145,178,0.01)' },
|
||||
],
|
||||
},
|
||||
},
|
||||
markArea: markArea.data
|
||||
? { silent: true, data: markArea.data }
|
||||
: undefined,
|
||||
markPoint:
|
||||
referenceMin != null && referenceMax != null
|
||||
? {
|
||||
data: data
|
||||
.filter((d) => d.value < referenceMin || d.value > referenceMax)
|
||||
.map((d) => ({
|
||||
coord: [data.indexOf(d), d.value],
|
||||
itemStyle: { color: '#DC2626' },
|
||||
symbolSize: 12,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -77,7 +106,7 @@ export default function TrendChart({ data, referenceMin, referenceMax, unit = ''
|
||||
if (chartRef.current && data && data.length > 0) {
|
||||
const option = getOption();
|
||||
if (option) {
|
||||
chartRef.current.refresh(option);
|
||||
chartRef.current.setOption(option);
|
||||
}
|
||||
}
|
||||
}, [data, getOption]);
|
||||
@@ -92,7 +121,7 @@ export default function TrendChart({ data, referenceMin, referenceMax, unit = ''
|
||||
|
||||
return (
|
||||
<View className='trend-chart' style={{ height: `${height}rpx` }}>
|
||||
<EChart canvasId='trend-chart-canvas' ref={chartRef} />
|
||||
<EcCanvas canvasId='trend-chart-canvas' ref={chartRef} height={height} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,42 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
.detail-page {
|
||||
min-height: 100vh;
|
||||
background: #f1f5f9;
|
||||
padding: 16px;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.detail-type {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
@include section-title;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.detail-meta {
|
||||
@@ -26,27 +45,57 @@
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
// RichText 内部样式
|
||||
h1, h2, h3 {
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin: 24px 0 12px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 32px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: $pri-d;
|
||||
}
|
||||
}
|
||||
|
||||
.report-content {
|
||||
font-size: 14px;
|
||||
font-size: 28px;
|
||||
line-height: 1.8;
|
||||
color: #334155;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 60px 0;
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
padding: 120px 0;
|
||||
color: $tx3;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
@@ -1,65 +1,83 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
.ai-report-page {
|
||||
min-height: 100vh;
|
||||
background: #f1f5f9;
|
||||
padding: 16px;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
margin-bottom: 16px;
|
||||
@include section-title;
|
||||
}
|
||||
|
||||
.report-scroll {
|
||||
height: calc(100vh - 80px);
|
||||
height: calc(100vh - 100px);
|
||||
}
|
||||
|
||||
.report-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-type {
|
||||
font-size: 15px;
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
color: #1e293b;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.card-status {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
@include tag($bd-l, $tx2);
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
color: #16a34a;
|
||||
background: #dcfce7;
|
||||
@include tag($acc-l, $acc);
|
||||
}
|
||||
|
||||
.status-streaming {
|
||||
color: #2563eb;
|
||||
background: #dbeafe;
|
||||
@include tag($pri-l, $pri);
|
||||
}
|
||||
|
||||
.status-failed {
|
||||
color: #dc2626;
|
||||
background: #fee2e2;
|
||||
@include tag($dan-l, $dan);
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
color: #d97706;
|
||||
background: #fef3c7;
|
||||
@include tag($wrn-l, $wrn);
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
@@ -69,19 +87,19 @@
|
||||
}
|
||||
|
||||
.card-time {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.card-model {
|
||||
font-size: 11px;
|
||||
color: #cbd5e1;
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
padding: 16px 0;
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
padding: 24px 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.create-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding-bottom: 140px;
|
||||
padding-bottom: 160px;
|
||||
}
|
||||
|
||||
/* 步骤内容 */
|
||||
@@ -11,6 +12,10 @@
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
@include section-title;
|
||||
}
|
||||
|
||||
/* 科室宫格 */
|
||||
.dept-grid {
|
||||
display: grid;
|
||||
@@ -21,33 +26,62 @@
|
||||
.dept-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px 12px;
|
||||
padding: 28px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.2s;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&.dept-selected {
|
||||
border-color: $pri;
|
||||
background: $pri-l;
|
||||
}
|
||||
}
|
||||
|
||||
.dept-card.dept-selected {
|
||||
border-color: $pri;
|
||||
background: $pri-surface;
|
||||
.dept-initial-circle {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: $r;
|
||||
background: $pri-l;
|
||||
@include flex-center;
|
||||
|
||||
.dept-selected & {
|
||||
background: $pri;
|
||||
}
|
||||
}
|
||||
|
||||
.dept-icon {
|
||||
font-size: 40px;
|
||||
.dept-initial-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
|
||||
.dept-selected & {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.dept-label {
|
||||
font-size: 26px;
|
||||
font-size: 24px;
|
||||
color: $tx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 时段卡片 */
|
||||
/* 时段 */
|
||||
.slot-section {
|
||||
margin-top: 24px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.slot-section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.slot-grid {
|
||||
@@ -59,16 +93,26 @@
|
||||
.slot-card {
|
||||
background: $card;
|
||||
border-radius: $r-sm;
|
||||
padding: 16px 20px;
|
||||
padding: 20px 24px;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.2s;
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&.slot-few { border-color: $wrn; }
|
||||
&.slot-full { opacity: 0.5; background: $bd-l; }
|
||||
&.slot-selected { border-color: $pri; background: $pri-surface; }
|
||||
&.slot-few {
|
||||
border-color: $wrn;
|
||||
}
|
||||
&.slot-full {
|
||||
opacity: 0.5;
|
||||
background: $bd-l;
|
||||
}
|
||||
&.slot-selected {
|
||||
border-color: $pri;
|
||||
background: $pri-l;
|
||||
}
|
||||
}
|
||||
|
||||
.slot-time {
|
||||
@include serif-number;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
@@ -79,43 +123,70 @@
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.slot-few .slot-count { color: $wrn; }
|
||||
.slot-full .slot-count { color: $dan; }
|
||||
|
||||
.step-title {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 28px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 选择器卡片 */
|
||||
.picker-card {
|
||||
/* 确认卡片 (step 3 医生信息) */
|
||||
.confirm-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px 28px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
margin-bottom: 24px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.picker-value {
|
||||
.confirm-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.confirm-icon-wrap {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: $r-sm;
|
||||
background: $pri-l;
|
||||
@include flex-center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.confirm-icon-serif {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
.confirm-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.confirm-label {
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.confirm-value {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.picker-value.placeholder {
|
||||
color: $tx3;
|
||||
.confirm-dept-tag {
|
||||
@include tag($pri-l, $pri);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.picker-arrow {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
.confirm-dept-text {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
/* 医生列表 */
|
||||
@@ -132,28 +203,27 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.doctor-card.doctor-selected {
|
||||
border-color: $pri;
|
||||
background: $pri-surface;
|
||||
&.doctor-selected {
|
||||
border-color: $pri;
|
||||
background: $pri-l;
|
||||
}
|
||||
}
|
||||
|
||||
.doctor-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
border-radius: $r;
|
||||
background: $pri-l;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@include flex-center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.doctor-avatar-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
@@ -183,14 +253,23 @@
|
||||
}
|
||||
|
||||
.doctor-check {
|
||||
font-size: 32px;
|
||||
color: $pri;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: $r-pill;
|
||||
background: $pri;
|
||||
@include flex-center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.doctor-check-text {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 表单 */
|
||||
.form-group {
|
||||
margin-bottom: 28px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
@@ -200,17 +279,6 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-static {
|
||||
background: $card;
|
||||
border-radius: $r-sm;
|
||||
padding: 24px 28px;
|
||||
}
|
||||
|
||||
.form-static-text {
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
background: $card;
|
||||
border-radius: $r-sm;
|
||||
@@ -219,10 +287,11 @@
|
||||
color: $tx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
.empty-hint {
|
||||
padding: 80px 0;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -244,7 +313,7 @@
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background: $card;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@@ -261,7 +330,7 @@
|
||||
|
||||
.btn-next,
|
||||
.btn-submit {
|
||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||
background: $pri;
|
||||
}
|
||||
|
||||
.btn-disabled {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.detail-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding-bottom: 140px;
|
||||
padding-bottom: 160px;
|
||||
}
|
||||
|
||||
/* 顶部导航 */
|
||||
@@ -13,7 +14,8 @@
|
||||
justify-content: space-between;
|
||||
padding: 32px;
|
||||
padding-top: 48px;
|
||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||
background: $card;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
@@ -22,13 +24,15 @@
|
||||
|
||||
.back-text {
|
||||
font-size: 28px;
|
||||
color: white;
|
||||
color: $pri;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 34px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.header-placeholder {
|
||||
@@ -40,46 +44,48 @@
|
||||
background: $card;
|
||||
border-radius: $r-lg;
|
||||
padding: 40px 32px;
|
||||
margin: -20px 24px 24px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
margin: 20px 24px 24px;
|
||||
box-shadow: $shadow-md;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
.status-tag {
|
||||
@include tag($bd-l, $tx3);
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 32px;
|
||||
border-radius: 24px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.status-badge-text {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
border-radius: $r-pill;
|
||||
|
||||
&.tag-pending {
|
||||
background: $wrn-l;
|
||||
.status-badge-text { color: $wrn; }
|
||||
.status-tag-text { color: $wrn; }
|
||||
}
|
||||
|
||||
&.tag-confirmed {
|
||||
background: $acc-l;
|
||||
.status-badge-text { color: $acc; }
|
||||
.status-tag-text { color: $acc; }
|
||||
}
|
||||
|
||||
&.tag-cancelled {
|
||||
background: $bd-l;
|
||||
.status-badge-text { color: $tx3; }
|
||||
.status-tag-text { color: $tx3; }
|
||||
}
|
||||
|
||||
&.tag-completed {
|
||||
background: $pri-l;
|
||||
.status-badge-text { color: $pri; }
|
||||
.status-tag-text { color: $pri; }
|
||||
}
|
||||
}
|
||||
|
||||
.status-tag-text {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-doctor {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
@@ -96,22 +102,19 @@
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
@include section-title;
|
||||
margin-bottom: 24px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
padding: 18px 0;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
}
|
||||
|
||||
@@ -119,6 +122,26 @@
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.info-icon-serif {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 20px;
|
||||
color: $pri;
|
||||
background: $pri-l;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: $r-sm;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 26px;
|
||||
color: $tx2;
|
||||
@@ -130,7 +153,16 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-date {
|
||||
@include serif-number;
|
||||
}
|
||||
|
||||
.info-time {
|
||||
@include serif-number;
|
||||
}
|
||||
|
||||
.info-id {
|
||||
@include serif-number;
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
word-break: break-all;
|
||||
@@ -147,7 +179,8 @@
|
||||
}
|
||||
|
||||
.tips-title {
|
||||
font-size: 26px;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $wrn;
|
||||
margin-bottom: 12px;
|
||||
@@ -170,7 +203,7 @@
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background: $card;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
@@ -190,27 +223,3 @@
|
||||
font-weight: bold;
|
||||
color: $dan;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120px 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 30px;
|
||||
color: $tx2;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,36 @@
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/mixins.scss';
|
||||
|
||||
.appointment-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding-bottom: 140px;
|
||||
padding-bottom: 160px;
|
||||
}
|
||||
|
||||
/* 页头 */
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||
padding: 32px;
|
||||
padding-top: 48px;
|
||||
background: $card;
|
||||
padding: 48px 32px 36px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@include section-title;
|
||||
margin-bottom: 4px;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* 预约列表 */
|
||||
.appointment-list {
|
||||
padding: 0 24px;
|
||||
margin-top: -16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.appointment-card {
|
||||
@@ -28,7 +38,7 @@
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.card-top {
|
||||
@@ -38,84 +48,117 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.doctor-info {
|
||||
.doctor-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 16px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dept-initial {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: $r;
|
||||
background: $pri-l;
|
||||
@include flex-center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dept-initial-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
.doctor-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.doctor-name {
|
||||
font-size: 32px;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.department {
|
||||
font-size: 24px;
|
||||
.dept-tag {
|
||||
@include tag($pri-l, $pri);
|
||||
}
|
||||
|
||||
.dept-tag-text {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: $pri;
|
||||
background: $pri-l;
|
||||
padding: 4px 16px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
padding: 6px 20px;
|
||||
border-radius: 20px;
|
||||
|
||||
.status-tag-text {
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
@include tag($bd-l, $tx3);
|
||||
flex-shrink: 0;
|
||||
|
||||
&.tag-pending {
|
||||
background: $wrn-l;
|
||||
|
||||
.status-tag-text {
|
||||
color: $wrn;
|
||||
}
|
||||
.status-tag-text { color: $wrn; }
|
||||
}
|
||||
|
||||
&.tag-confirmed {
|
||||
background: $acc-l;
|
||||
|
||||
.status-tag-text {
|
||||
color: $acc;
|
||||
}
|
||||
.status-tag-text { color: $acc; }
|
||||
}
|
||||
|
||||
&.tag-cancelled {
|
||||
background: $bd-l;
|
||||
|
||||
.status-tag-text {
|
||||
color: $tx3;
|
||||
}
|
||||
.status-tag-text { color: $tx3; }
|
||||
}
|
||||
|
||||
&.tag-completed {
|
||||
background: $pri-l;
|
||||
|
||||
.status-tag-text {
|
||||
color: $pri;
|
||||
}
|
||||
.status-tag-text { color: $pri; }
|
||||
}
|
||||
}
|
||||
|
||||
.status-tag-text {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
height: 1px;
|
||||
background: $bd-l;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-bottom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid $bd-l;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
font-size: 26px;
|
||||
.info-icon-wrap {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: $r-sm;
|
||||
background: $bd-l;
|
||||
@include flex-center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-icon-serif {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 22px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
@@ -123,49 +166,22 @@
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120px 0 80px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 30px;
|
||||
color: $tx2;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.loading-tip {
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
.info-time {
|
||||
@include serif-number;
|
||||
color: $tx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 底部悬浮按钮 */
|
||||
.fab-btn {
|
||||
position: fixed;
|
||||
bottom: 60px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||
padding: 24px 64px;
|
||||
border-radius: 48px;
|
||||
box-shadow: 0 8px 24px rgba($pri, 0.4);
|
||||
background: $pri;
|
||||
padding: 24px 72px;
|
||||
border-radius: $r-pill;
|
||||
box-shadow: 0 8px 24px rgba($pri, 0.3);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
@@ -173,4 +189,5 @@
|
||||
font-size: 30px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
@@ -65,20 +65,20 @@
|
||||
// RichText 内部样式优化
|
||||
h1, h2, h3 {
|
||||
font-weight: bold;
|
||||
color: #134E4A;
|
||||
color: $tx;
|
||||
margin: 24px 0 12px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 28px;
|
||||
color: #134E4A;
|
||||
color: $tx;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
border-radius: 8px;
|
||||
border-radius: $r-sm;
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.article-card-body {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.chat-page {
|
||||
display: flex;
|
||||
@@ -13,9 +14,10 @@
|
||||
align-items: center;
|
||||
padding: 24px 32px;
|
||||
background: $card;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
border-bottom: 1px solid $bd;
|
||||
|
||||
&__title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
color: $tx;
|
||||
@@ -45,17 +47,17 @@
|
||||
.msg-bubble {
|
||||
max-width: 70%;
|
||||
padding: 20px 24px;
|
||||
border-radius: 16px;
|
||||
border-radius: $r-lg;
|
||||
position: relative;
|
||||
|
||||
&--other {
|
||||
background: $card;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-left-radius: $r-sm;
|
||||
}
|
||||
|
||||
&--self {
|
||||
background: $pri;
|
||||
border-top-right-radius: 4px;
|
||||
border-top-right-radius: $r-sm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,14 +100,14 @@
|
||||
align-items: center;
|
||||
padding: 16px 24px;
|
||||
background: $card;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
border-top: 1px solid $bd;
|
||||
padding-bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
flex: 1;
|
||||
background: $bg;
|
||||
border-radius: 12px;
|
||||
border-radius: $r;
|
||||
padding: 16px 20px;
|
||||
font-size: 28px;
|
||||
margin-right: 16px;
|
||||
@@ -113,7 +115,7 @@
|
||||
|
||||
.chat-send-btn {
|
||||
background: $pri;
|
||||
border-radius: 12px;
|
||||
border-radius: $r;
|
||||
padding: 16px 28px;
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -132,7 +134,7 @@
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
background: $card;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
border-top: 1px solid $bd;
|
||||
|
||||
&__text {
|
||||
font-size: 26px;
|
||||
|
||||
@@ -1,48 +1,47 @@
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/mixins.scss';
|
||||
|
||||
.consultation-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
}
|
||||
|
||||
/* ─── 页头 ─── */
|
||||
.consultation-header {
|
||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||
padding: 32px;
|
||||
padding-top: 48px;
|
||||
color: white;
|
||||
padding: 48px 32px 36px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.consultation-header-title {
|
||||
font-size: 36px;
|
||||
.consultation-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
color: #fff;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.consultation-header-desc {
|
||||
.consultation-subtitle {
|
||||
font-size: 24px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
display: block;
|
||||
}
|
||||
|
||||
// ---- Loading / Error / Empty ----
|
||||
|
||||
.consultation-loading {
|
||||
padding: 120px 0;
|
||||
}
|
||||
|
||||
.consultation-error {
|
||||
/* ─── 居中容器 ─── */
|
||||
.consultation-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 120px 40px;
|
||||
}
|
||||
|
||||
.consultation-error-text {
|
||||
.consultation-error {
|
||||
font-size: 26px;
|
||||
color: $dan;
|
||||
}
|
||||
|
||||
/* ─── 空状态 ─── */
|
||||
.consultation-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -51,46 +50,56 @@
|
||||
padding: 160px 40px;
|
||||
}
|
||||
|
||||
.consultation-empty-icon {
|
||||
font-size: 100px;
|
||||
.empty-icon {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
background: $pri-l;
|
||||
@include flex-center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.consultation-empty-text {
|
||||
font-size: 36px;
|
||||
.empty-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 52px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 16px;
|
||||
color: $pri;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.consultation-empty-hint {
|
||||
.empty-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: $tx;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 26px;
|
||||
color: $tx3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// ---- Session List ----
|
||||
|
||||
.consultation-list {
|
||||
padding: 16px 24px;
|
||||
/* ─── 会话列表 ─── */
|
||||
.session-list {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
.consultation-session {
|
||||
.session-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
transition: transform 0.15s;
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.session-left {
|
||||
.session-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -105,7 +114,7 @@
|
||||
.session-subject {
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -113,24 +122,10 @@
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.session-status-active {
|
||||
font-size: 22px;
|
||||
color: $acc;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.session-status-pending {
|
||||
font-size: 22px;
|
||||
color: $wrn;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.session-status-closed {
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
white-space: nowrap;
|
||||
.session-tag {
|
||||
&.tag-ok { @include tag($acc-l, $acc); }
|
||||
&.tag-warn { @include tag($wrn-l, $wrn); }
|
||||
&.tag-default { @include tag($bd-l, $tx2); }
|
||||
}
|
||||
|
||||
.session-message {
|
||||
@@ -149,23 +144,20 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
// ---- Unread Badge ----
|
||||
|
||||
/* ─── 未读角标 ─── */
|
||||
.session-badge {
|
||||
background: $dan;
|
||||
border-radius: 999px;
|
||||
border-radius: $r-pill;
|
||||
min-width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px;
|
||||
@include flex-center;
|
||||
padding: 0 10px;
|
||||
margin-left: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.session-badge-text {
|
||||
font-size: 22px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -1,87 +1,129 @@
|
||||
@import '../../styles/variables.scss';
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.device-sync-page {
|
||||
min-height: 100vh;
|
||||
background: #F1F5F9;
|
||||
background: $bg;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.sync-header {
|
||||
background: linear-gradient(135deg, #0891B2, #0E7490);
|
||||
padding: 48px 24px 24px;
|
||||
color: #fff;
|
||||
background: $pri;
|
||||
padding: 48px 32px 32px;
|
||||
color: $card;
|
||||
}
|
||||
|
||||
.sync-header-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sync-section {
|
||||
padding: 16px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.sync-hero {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 32px 16px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding: 48px 24px;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.sync-hero-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: $pri-l;
|
||||
@include flex-center;
|
||||
margin-bottom: 20px;
|
||||
color: $pri;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sync-hero-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1E293B;
|
||||
margin-bottom: 4px;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 34px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sync-hero-desc {
|
||||
font-size: 13px;
|
||||
color: #64748B;
|
||||
font-size: 26px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.sync-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #0891B2;
|
||||
border-radius: 8px;
|
||||
padding: 12px 24px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
@include flex-center;
|
||||
background: $pri;
|
||||
border-radius: $r-sm;
|
||||
padding: 20px 40px;
|
||||
margin: 12px 0;
|
||||
|
||||
.sync-action--primary {
|
||||
flex: 1;
|
||||
background: #0891B2;
|
||||
}
|
||||
&--primary {
|
||||
flex: 1;
|
||||
background: $pri;
|
||||
}
|
||||
|
||||
.sync-action--danger {
|
||||
flex: 1;
|
||||
background: #EF4444;
|
||||
margin-left: 12px;
|
||||
&--danger {
|
||||
flex: 1;
|
||||
background: $dan;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.sync-action-text {
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
color: $card;
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sync-device-list {
|
||||
margin-top: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.sync-section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
margin-bottom: 8px;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -89,10 +131,11 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 8px;
|
||||
background: $card;
|
||||
border-radius: $r-sm;
|
||||
padding: 24px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.sync-device-info {
|
||||
@@ -101,135 +144,149 @@
|
||||
}
|
||||
|
||||
.sync-device-name {
|
||||
font-size: 15px;
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
color: #1E293B;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.sync-device-adapter {
|
||||
font-size: 12px;
|
||||
color: #94A3B8;
|
||||
margin-top: 2px;
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.sync-device-rssi {
|
||||
font-size: 12px;
|
||||
color: #64748B;
|
||||
font-size: 22px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.sync-status-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 12px;
|
||||
background: $card;
|
||||
border-radius: $r-sm;
|
||||
padding: 24px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.sync-status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
margin-right: 8px;
|
||||
background: #94A3B8;
|
||||
}
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
margin-right: 16px;
|
||||
background: $tx3;
|
||||
|
||||
.sync-status-dot--connected {
|
||||
background: #22C55E;
|
||||
&--connected {
|
||||
background: $acc;
|
||||
}
|
||||
}
|
||||
|
||||
.sync-status-text {
|
||||
font-size: 14px;
|
||||
color: #1E293B;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.sync-readings-panel {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 12px;
|
||||
background: $card;
|
||||
border-radius: $r-sm;
|
||||
padding: 24px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.sync-reading-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #F1F5F9;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sync-reading-type {
|
||||
font-size: 13px;
|
||||
color: #64748B;
|
||||
font-size: 26px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.sync-reading-value {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #0891B2;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
@include serif-number;
|
||||
}
|
||||
|
||||
.sync-readings-count {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #94A3B8;
|
||||
margin-top: 12px;
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sync-actions-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.sync-error {
|
||||
margin: 16px;
|
||||
padding: 12px 16px;
|
||||
background: #FEF2F2;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #FECACA;
|
||||
margin: 24px;
|
||||
padding: 20px 24px;
|
||||
background: $dan-l;
|
||||
border-radius: $r-sm;
|
||||
}
|
||||
|
||||
.sync-error-text {
|
||||
font-size: 13px;
|
||||
color: #DC2626;
|
||||
font-size: 26px;
|
||||
color: $dan;
|
||||
}
|
||||
|
||||
.sync-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 48px 16px;
|
||||
@include flex-center;
|
||||
padding: 64px 24px;
|
||||
}
|
||||
|
||||
.sync-loading-text {
|
||||
font-size: 14px;
|
||||
color: #64748B;
|
||||
font-size: 28px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.sync-result-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 32px 16px;
|
||||
margin-bottom: 16px;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 48px 24px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.sync-result-icon {
|
||||
font-size: 40px;
|
||||
color: #22C55E;
|
||||
margin-bottom: 8px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: $acc-l;
|
||||
@include flex-center;
|
||||
color: $acc;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.sync-result-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1E293B;
|
||||
margin-bottom: 4px;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 34px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sync-result-count {
|
||||
font-size: 13px;
|
||||
color: #64748B;
|
||||
font-size: 26px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ export default function DeviceSync() {
|
||||
const renderIdle = () => (
|
||||
<View className="sync-section">
|
||||
<View className="sync-hero">
|
||||
<Text className="sync-hero-icon">⌚</Text>
|
||||
<Text className="sync-hero-icon">D</Text>
|
||||
<Text className="sync-hero-title">设备同步</Text>
|
||||
<Text className="sync-hero-desc">连接智能手环,自动采集健康数据</Text>
|
||||
</View>
|
||||
@@ -176,7 +176,7 @@ export default function DeviceSync() {
|
||||
const renderDone = () => (
|
||||
<View className="sync-section">
|
||||
<View className="sync-result-card">
|
||||
<Text className="sync-result-icon">✓</Text>
|
||||
<Text className="sync-result-icon">V</Text>
|
||||
<Text className="sync-result-title">同步完成</Text>
|
||||
<Text className="sync-result-count">成功上传 {syncCount} 条数据</Text>
|
||||
</View>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
@import '../../../../styles/variables.scss';
|
||||
@import '../../../../styles/mixins.scss';
|
||||
|
||||
.chat-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background: #f0f4f8;
|
||||
background: $bg;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
@@ -10,18 +13,19 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24px 32px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
background: $card;
|
||||
border-bottom: 1px solid $bd;
|
||||
|
||||
&__title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
&__close {
|
||||
font-size: 26px;
|
||||
color: #ef4444;
|
||||
color: $dan;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
}
|
||||
@@ -44,35 +48,36 @@
|
||||
.msg-bubble {
|
||||
max-width: 70%;
|
||||
padding: 20px 24px;
|
||||
border-radius: 16px;
|
||||
border-radius: $r-lg;
|
||||
position: relative;
|
||||
|
||||
&--other {
|
||||
background: #fff;
|
||||
background: $card;
|
||||
border-top-left-radius: 4px;
|
||||
}
|
||||
|
||||
&--self {
|
||||
background: #0891b2;
|
||||
background: $pri;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-text {
|
||||
font-size: 28px;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
display: block;
|
||||
line-height: 1.6;
|
||||
word-break: break-all;
|
||||
|
||||
.msg-bubble--self & {
|
||||
color: #fff;
|
||||
color: $card;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-time {
|
||||
@include serif-number;
|
||||
font-size: 20px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
text-align: right;
|
||||
@@ -88,7 +93,7 @@
|
||||
|
||||
&__text {
|
||||
font-size: 26px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,23 +101,23 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 24px;
|
||||
background: #fff;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
background: $card;
|
||||
border-top: 1px solid $bd;
|
||||
padding-bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
flex: 1;
|
||||
background: #f1f5f9;
|
||||
border-radius: 12px;
|
||||
background: $bd-l;
|
||||
border-radius: $r;
|
||||
padding: 16px 20px;
|
||||
font-size: 28px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.chat-send-btn {
|
||||
background: #0891b2;
|
||||
border-radius: 12px;
|
||||
background: $pri;
|
||||
border-radius: $r;
|
||||
padding: 16px 28px;
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -122,7 +127,7 @@
|
||||
|
||||
&__text {
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
color: $card;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
@@ -130,11 +135,11 @@
|
||||
.chat-closed-bar {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
background: $card;
|
||||
border-top: 1px solid $bd;
|
||||
|
||||
&__text {
|
||||
font-size: 26px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.consultation-page {
|
||||
min-height: 100vh;
|
||||
background: #f0f4f8;
|
||||
background: $bg;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
background: $card;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
border-bottom: 1px solid $bd;
|
||||
}
|
||||
|
||||
.tab {
|
||||
@@ -15,11 +18,11 @@
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
font-size: 28px;
|
||||
color: #64748b;
|
||||
color: $tx2;
|
||||
position: relative;
|
||||
|
||||
&--active {
|
||||
color: #0891b2;
|
||||
color: $pri;
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
@@ -29,7 +32,7 @@
|
||||
left: 30%;
|
||||
right: 30%;
|
||||
height: 4px;
|
||||
background: #0891b2;
|
||||
background: $pri;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
@@ -43,14 +46,14 @@
|
||||
}
|
||||
|
||||
.session-card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
background: $card;
|
||||
border-radius: $r-lg;
|
||||
padding: 28px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: $shadow-sm;
|
||||
position: relative;
|
||||
|
||||
&:active {
|
||||
background: #f8fafc;
|
||||
background: $bd-l;
|
||||
}
|
||||
|
||||
&__top {
|
||||
@@ -63,7 +66,7 @@
|
||||
&__subject {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -72,8 +75,7 @@
|
||||
}
|
||||
|
||||
&__status {
|
||||
padding: 4px 14px;
|
||||
border-radius: 12px;
|
||||
@include tag(transparent, $tx2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -90,21 +92,17 @@
|
||||
}
|
||||
|
||||
&__type {
|
||||
font-size: 24px;
|
||||
color: #0891b2;
|
||||
background: #e0f2fe;
|
||||
padding: 2px 12px;
|
||||
border-radius: 8px;
|
||||
@include tag($pri-l, $pri);
|
||||
}
|
||||
|
||||
&__time {
|
||||
font-size: 24px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
&__preview {
|
||||
font-size: 26px;
|
||||
color: #64748b;
|
||||
color: $tx2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -117,17 +115,16 @@
|
||||
right: 20px;
|
||||
min-width: 36px;
|
||||
height: 36px;
|
||||
background: #ef4444;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: $dan;
|
||||
border-radius: $r-pill;
|
||||
@include flex-center;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
&__badge-text {
|
||||
@include serif-number;
|
||||
font-size: 22px;
|
||||
color: #fff;
|
||||
color: $card;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
@@ -141,16 +138,16 @@
|
||||
|
||||
&__btn {
|
||||
font-size: 26px;
|
||||
color: #0891b2;
|
||||
color: $pri;
|
||||
padding: 12px 24px;
|
||||
|
||||
&.disabled {
|
||||
color: #cbd5e1;
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
font-size: 24px;
|
||||
color: #64748b;
|
||||
color: $tx2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
@import '../../../../styles/variables.scss';
|
||||
@import '../../../../styles/mixins.scss';
|
||||
|
||||
.followup-detail {
|
||||
min-height: 100vh;
|
||||
background: #f0f4f8;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
background: $card;
|
||||
border-radius: $r-lg;
|
||||
padding: 28px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
@include section-title;
|
||||
}
|
||||
|
||||
.task-header {
|
||||
@@ -28,22 +27,23 @@
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
&__status {
|
||||
font-size: 24px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 12px;
|
||||
border-radius: $r;
|
||||
font-weight: 500;
|
||||
|
||||
&--pending { background: #fef3c7; color: #b45309; }
|
||||
&--in_progress { background: #e0f2fe; color: #0369a1; }
|
||||
&--completed { background: #dcfce7; color: #16a34a; }
|
||||
&--overdue { background: #fee2e2; color: #dc2626; }
|
||||
&--cancelled { background: #f1f5f9; color: #94a3b8; }
|
||||
&--pending { background: $wrn-l; color: $wrn; }
|
||||
&--in_progress { background: $pri-l; color: $pri; }
|
||||
&--completed { background: $acc-l; color: $acc; }
|
||||
&--overdue { background: $dan-l; color: $dan; }
|
||||
&--cancelled { background: $bd-l; color: $tx3; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,38 +61,38 @@
|
||||
|
||||
.info-label {
|
||||
font-size: 22px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 26px;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-template {
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
background: #f8fafc;
|
||||
border-radius: 12px;
|
||||
background: $bd-l;
|
||||
border-radius: $r;
|
||||
|
||||
&__label {
|
||||
font-size: 22px;
|
||||
color: #64748b;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 26px;
|
||||
color: #334155;
|
||||
color: $tx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
.record-item {
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
@@ -100,14 +100,14 @@
|
||||
|
||||
&__date {
|
||||
font-size: 22px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 26px;
|
||||
color: #334155;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.5;
|
||||
@@ -117,10 +117,10 @@
|
||||
.start-btn {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
background: #0891b2;
|
||||
border-radius: 12px;
|
||||
background: $pri;
|
||||
border-radius: $r;
|
||||
margin-bottom: 24px;
|
||||
color: #fff;
|
||||
color: $card;
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -131,7 +131,7 @@
|
||||
|
||||
.form-label {
|
||||
font-size: 26px;
|
||||
color: #475569;
|
||||
color: $tx2;
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
@@ -140,11 +140,11 @@
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
min-height: 160px;
|
||||
background: #f8fafc;
|
||||
border-radius: 12px;
|
||||
background: $bd-l;
|
||||
border-radius: $r;
|
||||
padding: 16px 20px;
|
||||
font-size: 26px;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.6;
|
||||
}
|
||||
@@ -152,16 +152,16 @@
|
||||
.form-date {
|
||||
width: 100%;
|
||||
padding: 16px 20px;
|
||||
background: #f8fafc;
|
||||
border-radius: 12px;
|
||||
background: $bd-l;
|
||||
border-radius: $r;
|
||||
font-size: 26px;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background: #0891b2;
|
||||
border-radius: 12px;
|
||||
background: $pri;
|
||||
border-radius: $r;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
@@ -172,7 +172,7 @@
|
||||
|
||||
&__text {
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
color: $card;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
@@ -180,6 +180,6 @@
|
||||
.error-text {
|
||||
text-align: center;
|
||||
padding: 80px 32px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.followup-page {
|
||||
min-height: 100vh;
|
||||
background: #f0f4f8;
|
||||
background: $bg;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
background: $card;
|
||||
padding: 0 12px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
border-bottom: 1px solid $bd;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -16,12 +19,12 @@
|
||||
display: inline-block;
|
||||
padding: 24px 16px;
|
||||
font-size: 26px;
|
||||
color: #64748b;
|
||||
color: $tx2;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--active {
|
||||
color: #0891b2;
|
||||
color: $pri;
|
||||
font-weight: 600;
|
||||
|
||||
&::after {
|
||||
@@ -31,7 +34,7 @@
|
||||
left: 20%;
|
||||
right: 20%;
|
||||
height: 4px;
|
||||
background: #0891b2;
|
||||
background: $pri;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
@@ -42,7 +45,7 @@
|
||||
|
||||
text {
|
||||
font-size: 24px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,13 +57,13 @@
|
||||
}
|
||||
|
||||
.task-card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
background: $card;
|
||||
border-radius: $r-lg;
|
||||
padding: 28px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
background: #f8fafc;
|
||||
background: $bd-l;
|
||||
}
|
||||
|
||||
&__header {
|
||||
@@ -71,21 +74,21 @@
|
||||
}
|
||||
|
||||
&__type {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
&__status {
|
||||
padding: 4px 14px;
|
||||
border-radius: 12px;
|
||||
@include tag(transparent, $tx2);
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__patient {
|
||||
font-size: 26px;
|
||||
color: #475569;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@@ -97,6 +100,6 @@
|
||||
|
||||
&__date {
|
||||
font-size: 24px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/mixins.scss';
|
||||
|
||||
.doctor-home {
|
||||
min-height: 100vh;
|
||||
background: #f0f4f8;
|
||||
background: $bg;
|
||||
padding: 32px;
|
||||
padding-bottom: 120px;
|
||||
|
||||
@@ -9,23 +12,21 @@
|
||||
}
|
||||
|
||||
&__title {
|
||||
@include section-title;
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__greeting {
|
||||
font-size: 28px;
|
||||
color: #64748b;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__date {
|
||||
font-size: 24px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
&__section {
|
||||
@@ -33,11 +34,7 @@
|
||||
}
|
||||
|
||||
&__section-title {
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
color: #334155;
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
@include section-title;
|
||||
}
|
||||
|
||||
&__grid {
|
||||
@@ -47,11 +44,11 @@
|
||||
}
|
||||
|
||||
&__card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
background: $card;
|
||||
border-radius: $r-lg;
|
||||
padding: 28px 24px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: $shadow-md;
|
||||
transition: transform 0.15s;
|
||||
|
||||
&:active {
|
||||
@@ -59,23 +56,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__card-icon {
|
||||
font-size: 36px;
|
||||
display: block;
|
||||
&__card-initial {
|
||||
display: inline-flex;
|
||||
@include flex-center;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: $r;
|
||||
background: $pri-l;
|
||||
color: $pri;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__card-num {
|
||||
@include serif-number;
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__card-label {
|
||||
font-size: 24px;
|
||||
color: #64748b;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
&__quick-actions {
|
||||
@@ -90,7 +96,7 @@
|
||||
}
|
||||
|
||||
&__logout {
|
||||
color: #ef4444;
|
||||
color: $dan;
|
||||
font-size: 28px;
|
||||
padding: 16px 48px;
|
||||
display: inline-block;
|
||||
@@ -99,24 +105,33 @@
|
||||
|
||||
.quick-action {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
background: $card;
|
||||
border-radius: $r-lg;
|
||||
padding: 28px 20px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: $shadow-md;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 40px;
|
||||
display: block;
|
||||
&__initial {
|
||||
display: inline-flex;
|
||||
@include flex-center;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: $r;
|
||||
background: $acc-l;
|
||||
color: $acc;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 24px;
|
||||
color: #475569;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,22 +9,26 @@ import './index.scss';
|
||||
interface CardConfig {
|
||||
key: keyof doctorApi.DoctorDashboard;
|
||||
label: string;
|
||||
icon: string;
|
||||
initial: string;
|
||||
route: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
const CARDS: CardConfig[] = [
|
||||
{ key: 'total_patients', label: '我的患者', icon: '👥', route: '/pages/doctor/patients/index', color: '#0891b2' },
|
||||
{ key: 'unread_messages', label: '未读消息', icon: '💬', route: '/pages/doctor/consultation/index', color: '#f59e0b' },
|
||||
{ key: 'pending_follow_ups', label: '待处理随访', icon: '📋', route: '/pages/doctor/followup/index', color: '#8b5cf6' },
|
||||
{ key: 'today_consultations', label: '今日咨询', icon: '🩺', route: '/pages/doctor/consultation/index', color: '#10b981' },
|
||||
{ key: 'total_patients', label: '我的患者', initial: '患', route: '/pages/doctor/patients/index' },
|
||||
{ key: 'unread_messages', label: '未读消息', initial: '消', route: '/pages/doctor/consultation/index' },
|
||||
{ key: 'pending_follow_ups', label: '待处理随访', initial: '随', route: '/pages/doctor/followup/index' },
|
||||
{ key: 'today_consultations', label: '今日咨询', initial: '诊', route: '/pages/doctor/consultation/index' },
|
||||
];
|
||||
|
||||
const HEALTH_CARDS: CardConfig[] = [
|
||||
{ key: 'pending_dialysis_review', label: '待审透析', icon: '💧', route: '/pages/doctor/patients/index', color: '#0ea5e9' },
|
||||
{ key: 'pending_lab_review', label: '待审化验', icon: '🔬', route: '/pages/doctor/report/index', color: '#f43f5e' },
|
||||
{ key: 'today_appointments', label: '今日预约', icon: '📅', route: '/pages/doctor/patients/index', color: '#14b8a6' },
|
||||
{ key: 'pending_dialysis_review', label: '待审透析', initial: '透', route: '/pages/doctor/patients/index' },
|
||||
{ key: 'pending_lab_review', label: '待审化验', initial: '化', route: '/pages/doctor/report/index' },
|
||||
{ key: 'today_appointments', label: '今日预约', initial: '约', route: '/pages/doctor/patients/index' },
|
||||
];
|
||||
|
||||
const QUICK_ACTIONS = [
|
||||
{ label: '化验审核', initial: '审', route: '/pages/doctor/report/index' },
|
||||
{ label: '患者查询', initial: '查', route: '/pages/doctor/patients/index' },
|
||||
];
|
||||
|
||||
export default function DoctorHome() {
|
||||
@@ -81,10 +85,9 @@ export default function DoctorHome() {
|
||||
<View
|
||||
key={card.key}
|
||||
className='doctor-home__card'
|
||||
style={`border-left: 6px solid ${card.color}`}
|
||||
onClick={() => handleCardClick(card)}
|
||||
>
|
||||
<Text className='doctor-home__card-icon'>{card.icon}</Text>
|
||||
<Text className='doctor-home__card-initial'>{card.initial}</Text>
|
||||
<Text className='doctor-home__card-num'>{getValue(card.key)}</Text>
|
||||
<Text className='doctor-home__card-label'>{card.label}</Text>
|
||||
</View>
|
||||
@@ -99,10 +102,9 @@ export default function DoctorHome() {
|
||||
<View
|
||||
key={card.key}
|
||||
className='doctor-home__card'
|
||||
style={`border-left: 6px solid ${card.color}`}
|
||||
onClick={() => handleCardClick(card)}
|
||||
>
|
||||
<Text className='doctor-home__card-icon'>{card.icon}</Text>
|
||||
<Text className='doctor-home__card-initial'>{card.initial}</Text>
|
||||
<Text className='doctor-home__card-num'>{getValue(card.key)}</Text>
|
||||
<Text className='doctor-home__card-label'>{card.label}</Text>
|
||||
</View>
|
||||
@@ -113,14 +115,16 @@ export default function DoctorHome() {
|
||||
<View className='doctor-home__section'>
|
||||
<Text className='doctor-home__section-title'>快捷操作</Text>
|
||||
<View className='doctor-home__quick-actions'>
|
||||
<View className='quick-action' onClick={() => Taro.navigateTo({ url: '/pages/doctor/report/index' })}>
|
||||
<Text className='quick-action__icon'>📊</Text>
|
||||
<Text className='quick-action__label'>化验审核</Text>
|
||||
</View>
|
||||
<View className='quick-action' onClick={() => Taro.navigateTo({ url: '/pages/doctor/patients/index' })}>
|
||||
<Text className='quick-action__icon'>🔍</Text>
|
||||
<Text className='quick-action__label'>患者查询</Text>
|
||||
</View>
|
||||
{QUICK_ACTIONS.map((action) => (
|
||||
<View
|
||||
key={action.route}
|
||||
className='quick-action'
|
||||
onClick={() => Taro.navigateTo({ url: action.route })}
|
||||
>
|
||||
<Text className='quick-action__initial'>{action.initial}</Text>
|
||||
<Text className='quick-action__label'>{action.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
@import '../../../../styles/variables.scss';
|
||||
@import '../../../../styles/mixins.scss';
|
||||
|
||||
.patient-detail {
|
||||
min-height: 100vh;
|
||||
background: #f0f4f8;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
background: $card;
|
||||
border-radius: $r-lg;
|
||||
padding: 28px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
@include section-title;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
@@ -35,25 +34,25 @@
|
||||
|
||||
.info-label {
|
||||
font-size: 22px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28px;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.warning-card {
|
||||
background: #fef3c7;
|
||||
border-radius: 12px;
|
||||
background: $wrn-l;
|
||||
border-radius: $r;
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.warning-label {
|
||||
font-size: 22px;
|
||||
color: #b45309;
|
||||
color: $wrn;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
@@ -61,7 +60,7 @@
|
||||
|
||||
.warning-text {
|
||||
font-size: 26px;
|
||||
color: #92400e;
|
||||
color: $pri-d;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
@@ -70,14 +69,14 @@
|
||||
|
||||
.info-block-label {
|
||||
font-size: 22px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.info-block-text {
|
||||
font-size: 26px;
|
||||
color: #334155;
|
||||
color: $tx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@@ -89,23 +88,24 @@
|
||||
}
|
||||
|
||||
.vital-item {
|
||||
background: #f0f9ff;
|
||||
border-radius: 12px;
|
||||
background: $pri-l;
|
||||
border-radius: $r;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vital-value {
|
||||
@include serif-number;
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
color: #0891b2;
|
||||
color: $pri;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.vital-label {
|
||||
font-size: 22px;
|
||||
color: #64748b;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.stat-row {
|
||||
@@ -117,29 +117,30 @@
|
||||
|
||||
.stat-label {
|
||||
font-size: 26px;
|
||||
color: #475569;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
@include serif-number;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
|
||||
&--warn {
|
||||
color: #f59e0b;
|
||||
color: $wrn;
|
||||
}
|
||||
}
|
||||
|
||||
.lab-item {
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #f8fafc;
|
||||
background: $bd-l;
|
||||
}
|
||||
|
||||
&__header {
|
||||
@@ -152,17 +153,17 @@
|
||||
&__type {
|
||||
font-size: 26px;
|
||||
font-weight: 500;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
&__date {
|
||||
font-size: 24px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
&__abnormal {
|
||||
font-size: 24px;
|
||||
color: #ef4444;
|
||||
color: $dan;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
@@ -176,9 +177,9 @@
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
background: #0891b2;
|
||||
color: #fff;
|
||||
border-radius: $r;
|
||||
background: $pri;
|
||||
color: $card;
|
||||
font-size: 26px;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -187,13 +188,13 @@
|
||||
}
|
||||
|
||||
text {
|
||||
color: #fff;
|
||||
color: $card;
|
||||
}
|
||||
}
|
||||
|
||||
.error-text {
|
||||
text-align: center;
|
||||
padding: 80px 32px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.patient-list-page {
|
||||
min-height: 100vh;
|
||||
background: #f0f4f8;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
@@ -9,12 +12,13 @@
|
||||
margin-bottom: 20px;
|
||||
|
||||
.search-input {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 20px 24px;
|
||||
font-size: 28px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,15 +31,15 @@
|
||||
.tag-chip {
|
||||
display: inline-block;
|
||||
padding: 10px 24px;
|
||||
border-radius: 20px;
|
||||
background: #e2e8f0;
|
||||
border-radius: $r-pill;
|
||||
background: $bd-l;
|
||||
font-size: 24px;
|
||||
color: #475569;
|
||||
color: $tx2;
|
||||
margin-right: 16px;
|
||||
|
||||
&.active {
|
||||
background: #0891b2;
|
||||
color: #fff;
|
||||
background: $pri;
|
||||
color: $card;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +48,7 @@
|
||||
|
||||
text {
|
||||
font-size: 24px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,13 +59,13 @@
|
||||
}
|
||||
|
||||
.patient-card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
background: $card;
|
||||
border-radius: $r-lg;
|
||||
padding: 28px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
background: #f8fafc;
|
||||
background: $bd-l;
|
||||
}
|
||||
|
||||
&__header {
|
||||
@@ -73,13 +77,13 @@
|
||||
&__name {
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
font-size: 24px;
|
||||
color: #64748b;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
&__tags {
|
||||
@@ -90,26 +94,22 @@
|
||||
}
|
||||
|
||||
&__status {
|
||||
font-size: 22px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
@include tag($bg, $tx2);
|
||||
|
||||
&--active {
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
@include tag($acc-l, $acc);
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
background: #f1f5f9;
|
||||
color: #94a3b8;
|
||||
@include tag($bd-l, $tx3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.patient-tag {
|
||||
padding: 4px 14px;
|
||||
border-radius: 12px;
|
||||
background: #e0f2fe;
|
||||
border-radius: $r;
|
||||
background: $pri-l;
|
||||
|
||||
&__text {
|
||||
font-size: 22px;
|
||||
@@ -125,16 +125,16 @@
|
||||
|
||||
&__btn {
|
||||
font-size: 26px;
|
||||
color: #0891b2;
|
||||
color: $pri;
|
||||
padding: 12px 24px;
|
||||
|
||||
&.disabled {
|
||||
color: #cbd5e1;
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
font-size: 24px;
|
||||
color: #64748b;
|
||||
color: $tx2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
@import '../../../../styles/variables.scss';
|
||||
@import '../../../../styles/mixins.scss';
|
||||
|
||||
.report-detail {
|
||||
min-height: 100vh;
|
||||
background: #f0f4f8;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
background: $card;
|
||||
border-radius: $r-lg;
|
||||
padding: 28px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
@include section-title;
|
||||
}
|
||||
|
||||
.report-header {
|
||||
@@ -28,31 +27,32 @@
|
||||
margin-bottom: 12px;
|
||||
|
||||
&__type {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
&__status {
|
||||
font-size: 24px;
|
||||
padding: 6px 16px;
|
||||
border-radius: 12px;
|
||||
border-radius: $r;
|
||||
font-weight: 500;
|
||||
|
||||
&--pending { background: #fef3c7; color: #b45309; }
|
||||
&--reviewed { background: #dcfce7; color: #16a34a; }
|
||||
&--pending { background: $wrn-l; color: $wrn; }
|
||||
&--reviewed { background: $acc-l; color: $acc; }
|
||||
}
|
||||
}
|
||||
|
||||
.report-date {
|
||||
font-size: 26px;
|
||||
color: #64748b;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.review-info {
|
||||
font-size: 24px;
|
||||
color: #10b981;
|
||||
color: $acc;
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
}
|
||||
@@ -64,19 +64,19 @@
|
||||
.indicator-row {
|
||||
display: flex;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
align-items: center;
|
||||
|
||||
&--header {
|
||||
border-bottom: 2px solid #e2e8f0;
|
||||
border-bottom: 2px solid $bd;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
&--abnormal {
|
||||
background: #fef2f2;
|
||||
background: $dan-l;
|
||||
margin: 0 -12px;
|
||||
padding: 16px 12px;
|
||||
border-radius: 8px;
|
||||
border-radius: $r-sm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,69 +85,71 @@
|
||||
|
||||
&--name {
|
||||
flex: 2;
|
||||
color: #334155;
|
||||
color: $tx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&--value {
|
||||
@include serif-number;
|
||||
flex: 2;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&--ref {
|
||||
@include serif-number;
|
||||
flex: 2;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&--flag {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
color: #10b981;
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
.indicator-row--abnormal &--flag {
|
||||
color: #ef4444;
|
||||
color: $dan;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.indicator-row--header & {
|
||||
font-size: 22px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.notes-display {
|
||||
background: #f0f9ff;
|
||||
border-radius: 12px;
|
||||
background: $pri-l;
|
||||
border-radius: $r;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.notes-text {
|
||||
font-size: 26px;
|
||||
color: #334155;
|
||||
color: $tx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.notes-textarea {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
background: #f8fafc;
|
||||
border-radius: 12px;
|
||||
background: $bd-l;
|
||||
border-radius: $r;
|
||||
padding: 20px;
|
||||
font-size: 26px;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.review-btn {
|
||||
background: #0891b2;
|
||||
border-radius: 12px;
|
||||
background: $pri;
|
||||
border-radius: $r;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
|
||||
@@ -157,7 +159,7 @@
|
||||
|
||||
&__text {
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
color: $card;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
@@ -165,6 +167,6 @@
|
||||
.error-text {
|
||||
text-align: center;
|
||||
padding: 80px 32px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.report-page {
|
||||
min-height: 100vh;
|
||||
background: #f0f4f8;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
@@ -9,12 +12,13 @@
|
||||
margin-bottom: 20px;
|
||||
|
||||
.search-input {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 20px 24px;
|
||||
font-size: 28px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +27,7 @@
|
||||
|
||||
text {
|
||||
font-size: 24px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,13 +38,13 @@
|
||||
}
|
||||
|
||||
.report-card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
background: $card;
|
||||
border-radius: $r-lg;
|
||||
padding: 28px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
background: #f8fafc;
|
||||
background: $bd-l;
|
||||
}
|
||||
|
||||
&__header {
|
||||
@@ -51,14 +55,15 @@
|
||||
}
|
||||
|
||||
&__type {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
&__date {
|
||||
font-size: 24px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
&__indicators {
|
||||
@@ -69,20 +74,16 @@
|
||||
|
||||
&__abnormal {
|
||||
font-size: 26px;
|
||||
color: #ef4444;
|
||||
color: $dan;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&__normal {
|
||||
font-size: 26px;
|
||||
color: #10b981;
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
&__reviewed {
|
||||
font-size: 22px;
|
||||
color: #0891b2;
|
||||
background: #e0f2fe;
|
||||
padding: 2px 12px;
|
||||
border-radius: 8px;
|
||||
@include tag($acc-l, $acc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,50 @@
|
||||
@import '../../styles/variables.scss';
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.events-page {
|
||||
min-height: 100vh;
|
||||
background: #f0f4f8;
|
||||
background: $bg;
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
|
||||
.events-header {
|
||||
background: linear-gradient(135deg, #0891b2, #06b6d4);
|
||||
padding: 40px 32px;
|
||||
color: #fff;
|
||||
background: $pri;
|
||||
padding: 48px 32px 32px;
|
||||
color: $card;
|
||||
|
||||
&__title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@@ -30,10 +63,10 @@
|
||||
}
|
||||
|
||||
.event-card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
@@ -43,29 +76,45 @@
|
||||
}
|
||||
|
||||
&__status {
|
||||
padding: 4px 14px;
|
||||
border-radius: 12px;
|
||||
@include tag($bd-l, $tx2);
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__status--published {
|
||||
@include tag($pri-l, $pri);
|
||||
}
|
||||
|
||||
&__status--ongoing {
|
||||
@include tag($acc-l, $acc);
|
||||
}
|
||||
|
||||
&__status--completed {
|
||||
@include tag($bd-l, $tx3);
|
||||
}
|
||||
|
||||
&__status--cancelled {
|
||||
@include tag($dan-l, $dan);
|
||||
}
|
||||
|
||||
&__points {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #f59e0b;
|
||||
font-weight: bold;
|
||||
color: $wrn;
|
||||
@include serif-number;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
font-size: 26px;
|
||||
color: #64748b;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.5;
|
||||
@@ -74,18 +123,18 @@
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__date {
|
||||
font-size: 24px;
|
||||
color: #475569;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
&__location {
|
||||
font-size: 24px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
@@ -93,26 +142,27 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
border-top: 1px solid $bd-l;
|
||||
}
|
||||
|
||||
&__participants {
|
||||
font-size: 24px;
|
||||
color: #94a3b8;
|
||||
color: $tx3;
|
||||
@include serif-number;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
background: #0891b2;
|
||||
border-radius: 12px;
|
||||
padding: 12px 28px;
|
||||
background: $pri;
|
||||
border-radius: $r-sm;
|
||||
padding: 16px 32px;
|
||||
|
||||
&--disabled {
|
||||
background: #cbd5e1;
|
||||
background: $bd;
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 26px;
|
||||
color: #fff;
|
||||
color: $card;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import Loading from '@/components/Loading';
|
||||
import EmptyState from '@/components/EmptyState';
|
||||
import './index.scss';
|
||||
|
||||
const STATUS_MAP: Record<string, { label: string; color: string }> = {
|
||||
published: { label: '报名中', color: '#0891b2' },
|
||||
ongoing: { label: '进行中', color: '#10b981' },
|
||||
completed: { label: '已结束', color: '#94a3b8' },
|
||||
cancelled: { label: '已取消', color: '#ef4444' },
|
||||
const STATUS_MAP: Record<string, { label: string; className: string }> = {
|
||||
published: { label: '报名中', className: 'event-card__status--published' },
|
||||
ongoing: { label: '进行中', className: 'event-card__status--ongoing' },
|
||||
completed: { label: '已结束', className: 'event-card__status--completed' },
|
||||
cancelled: { label: '已取消', className: 'event-card__status--cancelled' },
|
||||
};
|
||||
|
||||
export default function EventsPage() {
|
||||
@@ -70,14 +70,14 @@ export default function EventsPage() {
|
||||
) : (
|
||||
<View className='event-list'>
|
||||
{events.map((event) => {
|
||||
const st = STATUS_MAP[event.status] || { label: event.status, color: '#94a3b8' };
|
||||
const st = STATUS_MAP[event.status] || { label: event.status, className: '' };
|
||||
const isFull = event.max_participants != null && event.current_participants >= event.max_participants;
|
||||
const isRegistering = registering === event.id;
|
||||
|
||||
return (
|
||||
<View key={event.id} className='event-card'>
|
||||
<View className='event-card__header'>
|
||||
<View className='event-card__status' style={`background: ${st.color}20; color: ${st.color}`}>
|
||||
<View className={`event-card__status ${st.className}`}>
|
||||
<Text>{st.label}</Text>
|
||||
</View>
|
||||
<Text className='event-card__points'>+{event.points_reward} 积分</Text>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.detail-page {
|
||||
min-height: 100vh;
|
||||
@@ -12,14 +13,12 @@
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
@include section-title;
|
||||
font-size: 34px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -84,14 +83,11 @@
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
@include section-title;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@@ -100,7 +96,7 @@
|
||||
min-height: 200px;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
background: $bd-l;
|
||||
background: $bg;
|
||||
border-radius: $r-sm;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
@@ -111,26 +107,28 @@
|
||||
|
||||
.submit-btn {
|
||||
background: $pri;
|
||||
border-radius: $r-sm;
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn-text {
|
||||
font-size: 30px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@include flex-center;
|
||||
padding: 120px 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,54 +1,122 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.dm-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 60px;
|
||||
padding: 0 0 60px;
|
||||
}
|
||||
|
||||
.dm-section {
|
||||
/* ── hero ── */
|
||||
.dm-hero {
|
||||
padding: 48px 32px 36px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dm-hero-icon {
|
||||
@include flex-center;
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
border-radius: $r-lg;
|
||||
background: $pri-l;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.dm-hero-icon-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
.dm-hero-title {
|
||||
@include section-title;
|
||||
font-size: 36px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dm-hero-sub {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
/* ── card ── */
|
||||
.dm-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-md;
|
||||
padding: 28px;
|
||||
margin: 0 24px 20px;
|
||||
}
|
||||
|
||||
.dm-section-title {
|
||||
.dm-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.dm-card-serial {
|
||||
@include flex-center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: $r-sm;
|
||||
background: $pri-l;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dm-card-serial-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
.dm-card-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 12px;
|
||||
border-left: 4px solid $pri;
|
||||
}
|
||||
|
||||
.dm-date-picker {
|
||||
.dm-card-badge {
|
||||
@include tag($acc-l, $acc);
|
||||
font-size: 20px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* ── date picker ── */
|
||||
.dm-date-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: $bg;
|
||||
border-radius: $r-sm;
|
||||
padding: 20px 24px;
|
||||
padding: 22px 24px;
|
||||
}
|
||||
|
||||
.dm-date-value {
|
||||
font-size: 28px;
|
||||
color: $pri;
|
||||
@include serif-number;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dm-date-arrow {
|
||||
font-size: 28px;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
transform: rotate(180deg);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dm-bp-row {
|
||||
/* ── blood pressure group ── */
|
||||
.dm-bp-group {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dm-bp-field {
|
||||
@@ -56,16 +124,30 @@
|
||||
}
|
||||
|
||||
.dm-field-label {
|
||||
font-size: 24px;
|
||||
font-size: 22px;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.dm-bp-sep {
|
||||
font-size: 40px;
|
||||
.dm-bp-divider {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: 20px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.dm-bp-line {
|
||||
width: 16px;
|
||||
height: 1px;
|
||||
background: $bd;
|
||||
}
|
||||
|
||||
.dm-bp-slash {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 36px;
|
||||
color: $tx3;
|
||||
padding-bottom: 16px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@@ -73,29 +155,36 @@
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
margin-top: 10px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ── single row with unit ── */
|
||||
.dm-single-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.dm-field-unit-inline {
|
||||
.dm-input-flex {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dm-unit-inline {
|
||||
font-size: 26px;
|
||||
color: $tx3;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dm-input {
|
||||
/* ── input field ── */
|
||||
.dm-input-box {
|
||||
background: $bg;
|
||||
border-radius: $r-sm;
|
||||
padding: 20px 24px;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
width: 100%;
|
||||
@include serif-number;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -103,13 +192,14 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ── submit ── */
|
||||
.dm-submit {
|
||||
background: $pri;
|
||||
border-radius: $r-sm;
|
||||
padding: 24px;
|
||||
border-radius: $r;
|
||||
padding: 26px;
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
box-shadow: 0 4px 12px rgba(8, 145, 178, 0.3);
|
||||
margin: 40px 24px 0;
|
||||
box-shadow: $shadow-md;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:active {
|
||||
@@ -118,7 +208,7 @@
|
||||
}
|
||||
|
||||
.dm-submit-disabled {
|
||||
opacity: 0.6;
|
||||
opacity: 0.5;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@@ -126,12 +216,14 @@
|
||||
font-size: 32px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* ── reset ── */
|
||||
.dm-reset {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
margin-top: 16px;
|
||||
padding: 24px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.dm-reset-text {
|
||||
|
||||
@@ -87,7 +87,6 @@ export default function DailyMonitoring() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Zod 验证数值范围
|
||||
const parseNum = (v: string) => v ? parseFloat(v) : undefined;
|
||||
const fields = {
|
||||
morningSystolic: parseNum(morningSystolic),
|
||||
@@ -163,44 +162,72 @@ export default function DailyMonitoring() {
|
||||
}
|
||||
};
|
||||
|
||||
const isToday = recordDate === today;
|
||||
|
||||
return (
|
||||
<View className='dm-page'>
|
||||
{/* 页面标题 */}
|
||||
<View className='dm-hero'>
|
||||
<View className='dm-hero-icon'>
|
||||
<Text className='dm-hero-icon-text'>记</Text>
|
||||
</View>
|
||||
<Text className='dm-hero-title'>日常监测</Text>
|
||||
<Text className='dm-hero-sub'>每日健康数据上报</Text>
|
||||
</View>
|
||||
|
||||
{/* 日期选择 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>记录日期</Text>
|
||||
<View className='dm-card'>
|
||||
<View className='dm-card-header'>
|
||||
<View className='dm-card-serial'>
|
||||
<Text className='dm-card-serial-text'>1</Text>
|
||||
</View>
|
||||
<Text className='dm-card-title'>记录日期</Text>
|
||||
{isToday && (
|
||||
<Text className='dm-card-badge'>今日</Text>
|
||||
)}
|
||||
</View>
|
||||
<Picker
|
||||
mode='selector'
|
||||
range={dateList}
|
||||
value={dateIdx}
|
||||
onChange={(e) => setDateIdx(Number(e.detail.value))}
|
||||
>
|
||||
<View className='dm-date-picker'>
|
||||
<View className='dm-date-row'>
|
||||
<Text className='dm-date-value'>{recordDate}</Text>
|
||||
<Text className='dm-date-arrow'>▾</Text>
|
||||
<Text className='dm-date-arrow'>V</Text>
|
||||
</View>
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
{/* 晨起血压 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>晨起血压</Text>
|
||||
<View className='dm-bp-row'>
|
||||
<View className='dm-card'>
|
||||
<View className='dm-card-header'>
|
||||
<View className='dm-card-serial'>
|
||||
<Text className='dm-card-serial-text'>2</Text>
|
||||
</View>
|
||||
<Text className='dm-card-title'>晨起血压</Text>
|
||||
</View>
|
||||
<View className='dm-bp-group'>
|
||||
<View className='dm-bp-field'>
|
||||
<Text className='dm-field-label'>收缩压</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
className='dm-input-box'
|
||||
placeholder='如 120'
|
||||
value={morningSystolic}
|
||||
onInput={(e) => setMorningSystolic(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
<Text className='dm-bp-sep'>/</Text>
|
||||
<View className='dm-bp-divider'>
|
||||
<View className='dm-bp-line' />
|
||||
<Text className='dm-bp-slash'>/</Text>
|
||||
<View className='dm-bp-line' />
|
||||
</View>
|
||||
<View className='dm-bp-field'>
|
||||
<Text className='dm-field-label'>舒张压</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
className='dm-input-box'
|
||||
placeholder='如 80'
|
||||
value={morningDiastolic}
|
||||
onInput={(e) => setMorningDiastolic(e.detail.value)}
|
||||
@@ -211,25 +238,34 @@ export default function DailyMonitoring() {
|
||||
</View>
|
||||
|
||||
{/* 晚间血压 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>晚间血压</Text>
|
||||
<View className='dm-bp-row'>
|
||||
<View className='dm-card'>
|
||||
<View className='dm-card-header'>
|
||||
<View className='dm-card-serial'>
|
||||
<Text className='dm-card-serial-text'>3</Text>
|
||||
</View>
|
||||
<Text className='dm-card-title'>晚间血压</Text>
|
||||
</View>
|
||||
<View className='dm-bp-group'>
|
||||
<View className='dm-bp-field'>
|
||||
<Text className='dm-field-label'>收缩压</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
className='dm-input-box'
|
||||
placeholder='如 120'
|
||||
value={eveningSystolic}
|
||||
onInput={(e) => setEveningSystolic(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
<Text className='dm-bp-sep'>/</Text>
|
||||
<View className='dm-bp-divider'>
|
||||
<View className='dm-bp-line' />
|
||||
<Text className='dm-bp-slash'>/</Text>
|
||||
<View className='dm-bp-line' />
|
||||
</View>
|
||||
<View className='dm-bp-field'>
|
||||
<Text className='dm-field-label'>舒张压</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
className='dm-input-box'
|
||||
placeholder='如 80'
|
||||
value={eveningDiastolic}
|
||||
onInput={(e) => setEveningDiastolic(e.detail.value)}
|
||||
@@ -240,70 +276,95 @@ export default function DailyMonitoring() {
|
||||
</View>
|
||||
|
||||
{/* 体重 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>体重</Text>
|
||||
<View className='dm-card'>
|
||||
<View className='dm-card-header'>
|
||||
<View className='dm-card-serial'>
|
||||
<Text className='dm-card-serial-text'>4</Text>
|
||||
</View>
|
||||
<Text className='dm-card-title'>体重</Text>
|
||||
</View>
|
||||
<View className='dm-single-row'>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
className='dm-input-box dm-input-flex'
|
||||
placeholder='如 65.0'
|
||||
value={weight}
|
||||
onInput={(e) => setWeight(e.detail.value)}
|
||||
/>
|
||||
<Text className='dm-field-unit-inline'>kg</Text>
|
||||
<Text className='dm-unit-inline'>kg</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 血糖 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>血糖</Text>
|
||||
<View className='dm-card'>
|
||||
<View className='dm-card-header'>
|
||||
<View className='dm-card-serial'>
|
||||
<Text className='dm-card-serial-text'>5</Text>
|
||||
</View>
|
||||
<Text className='dm-card-title'>血糖</Text>
|
||||
</View>
|
||||
<View className='dm-single-row'>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
className='dm-input-box dm-input-flex'
|
||||
placeholder='如 5.6'
|
||||
value={bloodSugar}
|
||||
onInput={(e) => setBloodSugar(e.detail.value)}
|
||||
/>
|
||||
<Text className='dm-field-unit-inline'>mmol/L</Text>
|
||||
<Text className='dm-unit-inline'>mmol/L</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 饮水量 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>饮水量</Text>
|
||||
<View className='dm-card'>
|
||||
<View className='dm-card-header'>
|
||||
<View className='dm-card-serial'>
|
||||
<Text className='dm-card-serial-text'>6</Text>
|
||||
</View>
|
||||
<Text className='dm-card-title'>饮水量</Text>
|
||||
</View>
|
||||
<View className='dm-single-row'>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
className='dm-input-box dm-input-flex'
|
||||
placeholder='如 2000'
|
||||
value={fluidIntake}
|
||||
onInput={(e) => setFluidIntake(e.detail.value)}
|
||||
/>
|
||||
<Text className='dm-field-unit-inline'>ml</Text>
|
||||
<Text className='dm-unit-inline'>ml</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 尿量 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>尿量</Text>
|
||||
<View className='dm-card'>
|
||||
<View className='dm-card-header'>
|
||||
<View className='dm-card-serial'>
|
||||
<Text className='dm-card-serial-text'>7</Text>
|
||||
</View>
|
||||
<Text className='dm-card-title'>尿量</Text>
|
||||
</View>
|
||||
<View className='dm-single-row'>
|
||||
<Input
|
||||
type='digit'
|
||||
className='dm-input'
|
||||
className='dm-input-box dm-input-flex'
|
||||
placeholder='如 1500'
|
||||
value={urineOutput}
|
||||
onInput={(e) => setUrineOutput(e.detail.value)}
|
||||
/>
|
||||
<Text className='dm-field-unit-inline'>ml</Text>
|
||||
<Text className='dm-unit-inline'>ml</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 备注 */}
|
||||
<View className='dm-section'>
|
||||
<Text className='dm-section-title'>备注</Text>
|
||||
<View className='dm-card'>
|
||||
<View className='dm-card-header'>
|
||||
<View className='dm-card-serial'>
|
||||
<Text className='dm-card-serial-text'>8</Text>
|
||||
</View>
|
||||
<Text className='dm-card-title'>备注</Text>
|
||||
</View>
|
||||
<Input
|
||||
className='dm-input dm-input-full'
|
||||
className='dm-input-box dm-input-full'
|
||||
placeholder='如:头晕、乏力等(可选)'
|
||||
value={notes}
|
||||
onInput={(e) => setNotes(e.detail.value)}
|
||||
|
||||
@@ -1,46 +1,51 @@
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/mixins.scss';
|
||||
|
||||
.health-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding-bottom: 40px;
|
||||
padding-bottom: calc(120px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* ─── 页头 ─── */
|
||||
.health-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24px 32px;
|
||||
padding: 24px 32px 8px;
|
||||
}
|
||||
|
||||
.health-header-title {
|
||||
.health-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.health-header-btn {
|
||||
.health-add-btn {
|
||||
background: $pri;
|
||||
padding: 12px 28px;
|
||||
padding: 10px 28px;
|
||||
border-radius: $r-sm;
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.health-header-btn-text {
|
||||
.health-add-text {
|
||||
font-size: 26px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
// ---- Quick Actions (快捷操作) ----
|
||||
|
||||
.quick-actions {
|
||||
/* ─── 快捷操作 ─── */
|
||||
.health-actions-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 0 24px;
|
||||
margin-bottom: 24px;
|
||||
padding: 16px 24px 24px;
|
||||
}
|
||||
|
||||
.quick-action-item {
|
||||
.action-item {
|
||||
flex: 1;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
@@ -48,60 +53,54 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
transition: transform 0.15s;
|
||||
gap: 10px;
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.96);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-action-icon-wrap {
|
||||
.action-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
@include flex-center;
|
||||
|
||||
&.icon-primary { background: $pri-l; }
|
||||
&.icon-accent { background: $acc-l; }
|
||||
&.icon-warn { background: $wrn-l; }
|
||||
}
|
||||
|
||||
.quick-action-icon-primary {
|
||||
background: $pri-l;
|
||||
.action-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
|
||||
.icon-accent & { color: $acc; }
|
||||
.icon-warn & { color: $wrn; }
|
||||
}
|
||||
|
||||
.quick-action-icon-green {
|
||||
background: $acc-l;
|
||||
}
|
||||
|
||||
.quick-action-icon-orange {
|
||||
background: $wrn-l;
|
||||
}
|
||||
|
||||
.quick-action-icon {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.quick-action-label {
|
||||
.action-label {
|
||||
font-size: 24px;
|
||||
color: $tx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// ---- Checkin Status (打卡状态) ----
|
||||
|
||||
/* ─── 打卡卡片 ─── */
|
||||
.checkin-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
padding: 24px 28px;
|
||||
margin: 0 24px 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.checkin-left {
|
||||
.checkin-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
@@ -110,7 +109,7 @@
|
||||
.checkin-done {
|
||||
font-size: 28px;
|
||||
color: $acc;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.checkin-streak {
|
||||
@@ -124,164 +123,178 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.checkin-go-btn {
|
||||
.checkin-go {
|
||||
background: $pri;
|
||||
border-radius: $r-sm;
|
||||
padding: 12px 24px;
|
||||
padding: 12px 28px;
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.checkin-go-text {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
// ---- Health Grid (体征概览) ----
|
||||
/* ─── 通用 section ─── */
|
||||
.health-section {
|
||||
margin: 0 24px 28px;
|
||||
}
|
||||
|
||||
.health-grid {
|
||||
/* ─── 体征概览 ─── */
|
||||
.vitals-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
padding: 0 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.health-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
border-left: 6px solid $bd;
|
||||
transition: border-left-color 0.2s;
|
||||
|
||||
&.status-normal { border-left-color: $acc; }
|
||||
&.status-high { border-left-color: $dan; }
|
||||
&.status-low { border-left-color: $dan; }
|
||||
}
|
||||
|
||||
.health-card-label {
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.health-card-value {
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.health-card-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.health-card-unit {
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.health-card-status {
|
||||
font-size: 22px;
|
||||
|
||||
&.status-normal { color: $acc; }
|
||||
&.status-high, &.status-low { color: $dan; font-weight: bold; }
|
||||
}
|
||||
|
||||
.health-card-ref {
|
||||
font-size: 20px;
|
||||
color: $tx3;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
// ---- Trend Actions (趋势快捷入口) ----
|
||||
|
||||
.health-actions {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.action-card {
|
||||
flex: 1;
|
||||
.vital-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
padding: 24px 20px;
|
||||
box-shadow: $shadow-sm;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 40px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.action-label {
|
||||
font-size: 24px;
|
||||
.vital-label {
|
||||
font-size: 22px;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
// ---- Recent Daily Monitoring (最近日常监测) ----
|
||||
|
||||
.recent-section {
|
||||
padding: 0 24px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.recent-section-title {
|
||||
font-size: 28px;
|
||||
.vital-value {
|
||||
@include serif-number;
|
||||
font-size: 44px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 12px;
|
||||
border-left: 4px solid $pri;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.recent-record {
|
||||
.vital-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vital-unit {
|
||||
font-size: 20px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.vital-tag {
|
||||
@include tag($acc-l, $acc);
|
||||
|
||||
&.tag-warn {
|
||||
@include tag($wrn-l, $wrn);
|
||||
}
|
||||
}
|
||||
|
||||
.vital-ref {
|
||||
font-size: 20px;
|
||||
color: $tx3;
|
||||
margin-top: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ─── 趋势入口 ─── */
|
||||
.trend-row {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
overflow: hidden;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.trend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24px;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: $bd-l;
|
||||
}
|
||||
}
|
||||
|
||||
.trend-icon {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: $r-sm;
|
||||
background: $pri-l;
|
||||
@include flex-center;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.trend-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 26px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
.trend-label {
|
||||
flex: 1;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.trend-arrow {
|
||||
font-size: 32px;
|
||||
color: $tx3;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ─── 最近监测 ─── */
|
||||
.record-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.recent-record-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.record-date {
|
||||
font-size: 24px;
|
||||
color: $pri;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.recent-record-date {
|
||||
font-size: 26px;
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.recent-record-data {
|
||||
.record-data {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.recent-data-item {
|
||||
.record-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.recent-data-label {
|
||||
.record-item-label {
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.recent-data-value {
|
||||
.record-item-value {
|
||||
@include serif-number;
|
||||
font-size: 26px;
|
||||
color: $tx;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -9,11 +9,11 @@ import { trackEvent } from '../../services/analytics';
|
||||
import Loading from '../../components/Loading';
|
||||
import './index.scss';
|
||||
|
||||
function getStatusStyle(status?: string) {
|
||||
if (status === 'high') return { cls: 'status-high', label: '偏高 ▲' };
|
||||
if (status === 'low') return { cls: 'status-low', label: '偏低 ▼' };
|
||||
if (status === 'normal') return { cls: 'status-normal', label: '正常 ─' };
|
||||
return { cls: '', label: '' };
|
||||
function getStatusTag(status?: string) {
|
||||
if (status === 'high') return { label: '偏高', cls: 'tag-warn' };
|
||||
if (status === 'low') return { label: '偏低', cls: 'tag-warn' };
|
||||
if (status === 'normal') return { label: '正常', cls: 'tag-ok' };
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function Health() {
|
||||
@@ -32,7 +32,7 @@ export default function Health() {
|
||||
const status = await getCheckinStatus();
|
||||
setCheckinStatus(status);
|
||||
} catch {
|
||||
// ignore — points API may not be available
|
||||
// points API 可能不可用
|
||||
}
|
||||
|
||||
if (currentPatient) {
|
||||
@@ -40,7 +40,7 @@ export default function Health() {
|
||||
const resp = await listDailyMonitoring(currentPatient.id, { page: 1, page_size: 3 });
|
||||
setRecentRecords(resp.data || []);
|
||||
} catch {
|
||||
// ignore — daily monitoring API may not be available yet
|
||||
// daily monitoring API 可能不可用
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -57,10 +57,6 @@ export default function Health() {
|
||||
Taro.navigateTo({ url: `/pages/health/trend/index?indicator=${indicator}` });
|
||||
};
|
||||
|
||||
const goToTrendPage = () => {
|
||||
Taro.navigateTo({ url: '/pages/health/trend/index?indicator=blood_pressure_systolic' });
|
||||
};
|
||||
|
||||
const goToMall = () => {
|
||||
Taro.switchTab({ url: '/pages/mall/index' });
|
||||
};
|
||||
@@ -73,6 +69,18 @@ export default function Health() {
|
||||
{ label: '体重', value: summary.weight ? `${summary.weight.value}` : '--', unit: 'kg', indicator: 'weight', status: summary.weight?.status, ref: summary.weight?.reference_range },
|
||||
];
|
||||
|
||||
const quickActions = [
|
||||
{ label: '日常上报', char: '日', bg: 'icon-primary', action: goToDailyMonitoring },
|
||||
{ label: '体征录入', char: '录', bg: 'icon-accent', action: goToInput },
|
||||
{ label: '查看趋势', char: '势', bg: 'icon-warn', action: () => goToTrend('blood_pressure_systolic') },
|
||||
];
|
||||
|
||||
const trendLinks = [
|
||||
{ label: '血压趋势', indicator: 'blood_pressure_systolic', char: '压' },
|
||||
{ label: '心率趋势', indicator: 'heart_rate', char: '率' },
|
||||
{ label: '血糖趋势', indicator: 'blood_sugar_fasting', char: '糖' },
|
||||
];
|
||||
|
||||
const formatBp = (record: DailyMonitoring) => {
|
||||
const parts: string[] = [];
|
||||
if (record.morning_bp_systolic && record.morning_bp_diastolic) {
|
||||
@@ -86,42 +94,33 @@ export default function Health() {
|
||||
|
||||
return (
|
||||
<View className='health-page'>
|
||||
{/* 页头 */}
|
||||
<View className='health-header'>
|
||||
<Text className='health-header-title'>健康数据</Text>
|
||||
<View className='health-header-btn' onClick={goToInput}>
|
||||
<Text className='health-header-btn-text'>+ 录入</Text>
|
||||
<Text className='health-title'>健康数据</Text>
|
||||
<View className='health-add-btn' onClick={goToInput}>
|
||||
<Text className='health-add-text'>录入</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 快捷操作 */}
|
||||
<View className='quick-actions'>
|
||||
<View className='quick-action-item' onClick={goToDailyMonitoring}>
|
||||
<View className='quick-action-icon-wrap quick-action-icon-primary'>
|
||||
<Text className='quick-action-icon'>📋</Text>
|
||||
<View className='health-actions-row'>
|
||||
{quickActions.map((a) => (
|
||||
<View className='action-item' key={a.label} onClick={a.action}>
|
||||
<View className={`action-icon ${a.bg}`}>
|
||||
<Text className='action-char'>{a.char}</Text>
|
||||
</View>
|
||||
<Text className='action-label'>{a.label}</Text>
|
||||
</View>
|
||||
<Text className='quick-action-label'>日常上报</Text>
|
||||
</View>
|
||||
<View className='quick-action-item' onClick={goToInput}>
|
||||
<View className='quick-action-icon-wrap quick-action-icon-green'>
|
||||
<Text className='quick-action-icon'>💉</Text>
|
||||
</View>
|
||||
<Text className='quick-action-label'>体征录入</Text>
|
||||
</View>
|
||||
<View className='quick-action-item' onClick={goToTrendPage}>
|
||||
<View className='quick-action-icon-wrap quick-action-icon-orange'>
|
||||
<Text className='quick-action-icon'>📈</Text>
|
||||
</View>
|
||||
<Text className='quick-action-label'>查看趋势</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 打卡状态 */}
|
||||
{checkinStatus && (
|
||||
<View className='checkin-card'>
|
||||
<View className='checkin-left'>
|
||||
<View className='checkin-info'>
|
||||
{checkinStatus.checked_in_today ? (
|
||||
<>
|
||||
<Text className='checkin-done'>今日已打卡 ✓</Text>
|
||||
<Text className='checkin-done'>今日已打卡</Text>
|
||||
{checkinStatus.consecutive_days > 0 && (
|
||||
<Text className='checkin-streak'>连续 {checkinStatus.consecutive_days} 天</Text>
|
||||
)}
|
||||
@@ -131,7 +130,7 @@ export default function Health() {
|
||||
)}
|
||||
</View>
|
||||
{!checkinStatus.checked_in_today && (
|
||||
<View className='checkin-go-btn' onClick={goToMall}>
|
||||
<View className='checkin-go' onClick={goToMall}>
|
||||
<Text className='checkin-go-text'>去打卡</Text>
|
||||
</View>
|
||||
)}
|
||||
@@ -139,67 +138,68 @@ export default function Health() {
|
||||
)}
|
||||
|
||||
{/* 今日体征概览 */}
|
||||
{loading && !todaySummary ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<View className='health-grid'>
|
||||
{items.map((item) => {
|
||||
const style = getStatusStyle(item.status);
|
||||
return (
|
||||
<View className={`health-card ${style.cls}`} key={item.label} onClick={() => goToTrend(item.indicator)}>
|
||||
<Text className='health-card-label'>{item.label}</Text>
|
||||
<Text className='health-card-value'>{item.value}</Text>
|
||||
<View className='health-card-bottom'>
|
||||
<Text className='health-card-unit'>{item.unit}</Text>
|
||||
{style.label && <Text className={`health-card-status ${style.cls}`}>{style.label}</Text>}
|
||||
<View className='health-section'>
|
||||
<Text className='section-title'>今日体征</Text>
|
||||
{loading && !todaySummary ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<View className='vitals-grid'>
|
||||
{items.map((item) => {
|
||||
const tag = getStatusTag(item.status);
|
||||
return (
|
||||
<View className='vital-card' key={item.label} onClick={() => goToTrend(item.indicator)}>
|
||||
<Text className='vital-label'>{item.label}</Text>
|
||||
<Text className='vital-value'>{item.value}</Text>
|
||||
<View className='vital-bottom'>
|
||||
<Text className='vital-unit'>{item.unit}</Text>
|
||||
{tag && <Text className={`vital-tag ${tag.cls}`}>{tag.label}</Text>}
|
||||
</View>
|
||||
{item.ref && <Text className='vital-ref'>参考 {item.ref}</Text>}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 趋势快捷入口 */}
|
||||
<View className='health-section'>
|
||||
<Text className='section-title'>健康趋势</Text>
|
||||
<View className='trend-row'>
|
||||
{trendLinks.map((t) => (
|
||||
<View className='trend-item' key={t.label} onClick={() => goToTrend(t.indicator)}>
|
||||
<View className='trend-icon'>
|
||||
<Text className='trend-char'>{t.char}</Text>
|
||||
</View>
|
||||
{item.ref && <Text className='health-card-ref'>参考: {item.ref}</Text>}
|
||||
<Text className='trend-label'>{t.label}</Text>
|
||||
<Text className='trend-arrow'>›</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 原有趋势快捷入口 */}
|
||||
<View className='health-actions'>
|
||||
<View className='action-card' onClick={() => goToTrend('blood_pressure_systolic')}>
|
||||
<Text className='action-icon'>📈</Text>
|
||||
<Text className='action-label'>血压趋势</Text>
|
||||
</View>
|
||||
<View className='action-card' onClick={() => goToTrend('heart_rate')}>
|
||||
<Text className='action-icon'>❤️</Text>
|
||||
<Text className='action-label'>心率趋势</Text>
|
||||
</View>
|
||||
<View className='action-card' onClick={() => goToTrend('blood_sugar_fasting')}>
|
||||
<Text className='action-icon'>🩸</Text>
|
||||
<Text className='action-label'>血糖趋势</Text>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 最近日常监测记录 */}
|
||||
{/* 最近监测记录 */}
|
||||
{recentRecords.length > 0 && (
|
||||
<View className='recent-section'>
|
||||
<Text className='recent-section-title'>最近监测记录</Text>
|
||||
<View className='health-section'>
|
||||
<Text className='section-title'>最近监测</Text>
|
||||
{recentRecords.map((record) => (
|
||||
<View className='recent-record' key={record.id}>
|
||||
<View className='recent-record-header'>
|
||||
<Text className='recent-record-date'>{record.record_date}</Text>
|
||||
</View>
|
||||
<View className='recent-record-data'>
|
||||
<View className='recent-data-item'>
|
||||
<Text className='recent-data-label'>血压</Text>
|
||||
<Text className='recent-data-value'>{formatBp(record)}</Text>
|
||||
<View className='record-card' key={record.id}>
|
||||
<Text className='record-date'>{record.record_date}</Text>
|
||||
<View className='record-data'>
|
||||
<View className='record-item'>
|
||||
<Text className='record-item-label'>血压</Text>
|
||||
<Text className='record-item-value'>{formatBp(record)}</Text>
|
||||
</View>
|
||||
{record.weight != null && (
|
||||
<View className='recent-data-item'>
|
||||
<Text className='recent-data-label'>体重</Text>
|
||||
<Text className='recent-data-value'>{record.weight} kg</Text>
|
||||
<View className='record-item'>
|
||||
<Text className='record-item-label'>体重</Text>
|
||||
<Text className='record-item-value'>{record.weight} kg</Text>
|
||||
</View>
|
||||
)}
|
||||
{record.blood_sugar != null && (
|
||||
<View className='recent-data-item'>
|
||||
<Text className='recent-data-label'>血糖</Text>
|
||||
<Text className='recent-data-value'>{record.blood_sugar} mmol/L</Text>
|
||||
<View className='record-item'>
|
||||
<Text className='record-item-label'>血糖</Text>
|
||||
<Text className='record-item-value'>{record.blood_sugar} mmol/L</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -1,59 +1,204 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.input-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding: 0 0 60px;
|
||||
}
|
||||
|
||||
.input-section {
|
||||
margin-bottom: 32px;
|
||||
/* ── hero ── */
|
||||
.input-hero {
|
||||
padding: 48px 32px 36px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
.input-hero-icon {
|
||||
@include flex-center;
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
border-radius: $r-lg;
|
||||
background: $pri-l;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.input-hero-icon-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
.input-picker {
|
||||
.input-hero-title {
|
||||
@include section-title;
|
||||
font-size: 36px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.input-hero-sub {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
/* ── card ── */
|
||||
.input-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
box-shadow: $shadow-md;
|
||||
padding: 28px;
|
||||
margin: 0 24px 20px;
|
||||
}
|
||||
|
||||
.input-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.input-card-indicator {
|
||||
@include flex-center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: $r-sm;
|
||||
padding: 20px 24px;
|
||||
background: $acc-l;
|
||||
}
|
||||
|
||||
.input-card-indicator-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
.input-card-label {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
/* ── picker ── */
|
||||
.input-picker-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: $bg;
|
||||
border-radius: $r-sm;
|
||||
padding: 22px 24px;
|
||||
}
|
||||
|
||||
.input-picker-value {
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
@include serif-number;
|
||||
}
|
||||
|
||||
.picker-arrow {
|
||||
.input-picker-arrow {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
font-size: 28px;
|
||||
transform: rotate(180deg);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
background: $card;
|
||||
/* ── section title ── */
|
||||
.input-section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ── blood pressure group ── */
|
||||
.input-bp-group {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.input-bp-field {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.input-field-label {
|
||||
font-size: 22px;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.input-bp-divider {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: 20px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.input-bp-line {
|
||||
width: 16px;
|
||||
height: 1px;
|
||||
background: $bd;
|
||||
}
|
||||
|
||||
.input-bp-slash {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 36px;
|
||||
color: $tx3;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
/* ── input field ── */
|
||||
.input-field-box {
|
||||
background: $bg;
|
||||
border-radius: $r-sm;
|
||||
padding: 20px 24px;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
width: 100%;
|
||||
@include serif-number;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-submit {
|
||||
background: $pri;
|
||||
border-radius: $r-sm;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
margin-top: 48px;
|
||||
.input-field-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.submit-text {
|
||||
.input-field-unit {
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ── submit ── */
|
||||
.input-submit {
|
||||
background: $pri;
|
||||
border-radius: $r;
|
||||
padding: 26px;
|
||||
text-align: center;
|
||||
margin: 48px 24px 0;
|
||||
box-shadow: $shadow-md;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.input-submit-disabled {
|
||||
opacity: 0.5;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.input-submit-text {
|
||||
font-size: 32px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
@@ -99,71 +99,106 @@ export default function HealthInput() {
|
||||
}
|
||||
};
|
||||
|
||||
const indicatorInitial = INDICATORS[indicatorIdx].label.charAt(0);
|
||||
|
||||
return (
|
||||
<View className='input-page'>
|
||||
<View className='input-section'>
|
||||
<Text className='input-label'>指标类型</Text>
|
||||
{/* 页面标题 */}
|
||||
<View className='input-hero'>
|
||||
<View className='input-hero-icon'>
|
||||
<Text className='input-hero-icon-text'>录</Text>
|
||||
</View>
|
||||
<Text className='input-hero-title'>体征录入</Text>
|
||||
<Text className='input-hero-sub'>记录今日健康数据</Text>
|
||||
</View>
|
||||
|
||||
{/* 指标类型选择 */}
|
||||
<View className='input-card'>
|
||||
<View className='input-card-header'>
|
||||
<View className='input-card-indicator'>
|
||||
<Text className='input-card-indicator-char'>{indicatorInitial}</Text>
|
||||
</View>
|
||||
<Text className='input-card-label'>指标类型</Text>
|
||||
</View>
|
||||
<Picker
|
||||
mode='selector'
|
||||
range={INDICATORS.map((i) => i.label)}
|
||||
value={indicatorIdx}
|
||||
onChange={(e) => setIndicatorIdx(Number(e.detail.value))}
|
||||
>
|
||||
<View className='input-picker'>
|
||||
<Text>{INDICATORS[indicatorIdx].label}</Text>
|
||||
<Text className='picker-arrow'>▾</Text>
|
||||
<View className='input-picker-row'>
|
||||
<Text className='input-picker-value'>{INDICATORS[indicatorIdx].label}</Text>
|
||||
<Text className='input-picker-arrow'>V</Text>
|
||||
</View>
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
{/* 数值输入 */}
|
||||
{INDICATORS[indicatorIdx].value === 'blood_pressure' ? (
|
||||
<>
|
||||
<View className='input-section'>
|
||||
<Text className='input-label'>收缩压</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='input-field'
|
||||
placeholder='如 120'
|
||||
value={systolic}
|
||||
onInput={(e) => setSystolic(e.detail.value)}
|
||||
/>
|
||||
<View className='input-card'>
|
||||
<Text className='input-section-title'>血压数值</Text>
|
||||
<View className='input-bp-group'>
|
||||
<View className='input-bp-field'>
|
||||
<Text className='input-field-label'>收缩压</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='input-field-box'
|
||||
placeholder='如 120'
|
||||
value={systolic}
|
||||
onInput={(e) => setSystolic(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
<View className='input-bp-divider'>
|
||||
<View className='input-bp-line' />
|
||||
<Text className='input-bp-slash'>/</Text>
|
||||
<View className='input-bp-line' />
|
||||
</View>
|
||||
<View className='input-bp-field'>
|
||||
<Text className='input-field-label'>舒张压</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='input-field-box'
|
||||
placeholder='如 80'
|
||||
value={diastolic}
|
||||
onInput={(e) => setDiastolic(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View className='input-section'>
|
||||
<Text className='input-label'>舒张压</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='input-field'
|
||||
placeholder='如 80'
|
||||
value={diastolic}
|
||||
onInput={(e) => setDiastolic(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
<Text className='input-field-unit'>mmHg</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className='input-section'>
|
||||
<Text className='input-label'>数值</Text>
|
||||
<View className='input-card'>
|
||||
<Text className='input-section-title'>检测数值</Text>
|
||||
<Input
|
||||
type='digit'
|
||||
className='input-field'
|
||||
className='input-field-box input-field-full'
|
||||
placeholder='请输入数值'
|
||||
value={value}
|
||||
onInput={(e) => setValue(e.detail.value)}
|
||||
/>
|
||||
<Text className='input-field-unit'>
|
||||
{INDICATORS[indicatorIdx].label.match(/\((.+)\)/)?.[1] || ''}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View className='input-section'>
|
||||
<Text className='input-label'>备注(可选)</Text>
|
||||
{/* 备注 */}
|
||||
<View className='input-card'>
|
||||
<Text className='input-section-title'>备注</Text>
|
||||
<Input
|
||||
className='input-field'
|
||||
placeholder='如:饭后2小时'
|
||||
className='input-field-box input-field-full'
|
||||
placeholder='如:饭后2小时(可选)'
|
||||
value={note}
|
||||
onInput={(e) => setNote(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View className='input-submit' onClick={submitting ? undefined : handleSubmit}>
|
||||
<Text className='submit-text'>{submitting ? '提交中...' : '提交'}</Text>
|
||||
{/* 提交 */}
|
||||
<View
|
||||
className={`input-submit ${submitting ? 'input-submit-disabled' : ''}`}
|
||||
onClick={submitting ? undefined : handleSubmit}
|
||||
>
|
||||
<Text className='input-submit-text'>{submitting ? '提交中...' : '提交录入'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,113 +1,148 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.trend-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.trend-header {
|
||||
padding: 24px 32px;
|
||||
/* ── hero ── */
|
||||
.trend-hero {
|
||||
padding: 48px 32px 28px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.trend-title {
|
||||
font-size: 34px;
|
||||
.trend-hero-icon {
|
||||
@include flex-center;
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
border-radius: $r-lg;
|
||||
background: $pri-l;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.trend-hero-icon-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
.trend-hero-title {
|
||||
@include section-title;
|
||||
font-size: 36px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* ── range tabs ── */
|
||||
.trange-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
padding: 0 32px 28px;
|
||||
}
|
||||
|
||||
.trange-tab {
|
||||
padding: 12px 32px;
|
||||
border-radius: $r-pill;
|
||||
background: $card;
|
||||
box-shadow: $shadow-sm;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.trange-tab-active {
|
||||
background: $pri;
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
|
||||
.trange-tab-text {
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.trange-tab-text-active {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* ── chart card ── */
|
||||
.trend-chart-card {
|
||||
margin: 0 24px 20px;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
box-shadow: $shadow-md;
|
||||
padding: 24px;
|
||||
min-height: 300px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── reference card ── */
|
||||
.trend-ref-card {
|
||||
margin: 0 24px 20px;
|
||||
background: $acc-l;
|
||||
border-radius: $r;
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.trend-ref-label {
|
||||
font-size: 24px;
|
||||
color: $acc;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.trend-ref-value {
|
||||
font-size: 26px;
|
||||
color: $acc;
|
||||
@include serif-number;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ── list ── */
|
||||
.trend-list {
|
||||
margin: 0 24px;
|
||||
}
|
||||
|
||||
.trend-list-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.trend-tabs {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.trend-tab {
|
||||
padding: 10px 28px;
|
||||
border-radius: 20px;
|
||||
background: $card;
|
||||
}
|
||||
|
||||
.trend-tab.active {
|
||||
background: $pri;
|
||||
}
|
||||
|
||||
.trend-tab-text {
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.trend-tab.active .trend-tab-text {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.trend-chart {
|
||||
margin: 24px;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.trend-empty {
|
||||
font-size: 26px;
|
||||
color: $tx3;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.chart-bars {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
.chart-bar-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.chart-bar {
|
||||
width: 100%;
|
||||
background: linear-gradient(to top, $pri, $pri-l);
|
||||
border-radius: 4px 4px 0 0;
|
||||
min-height: 4px;
|
||||
}
|
||||
|
||||
.chart-bar-date {
|
||||
font-size: 18px;
|
||||
color: $tx3;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.trend-list {
|
||||
margin: 0 24px;
|
||||
}
|
||||
|
||||
.trend-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: $card;
|
||||
padding: 20px 24px;
|
||||
padding: 22px 28px;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:first-child {
|
||||
border-radius: $r $r 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 $r $r;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.trend-item:first-child {
|
||||
border-radius: $r $r 0 0;
|
||||
.trend-item-warn {
|
||||
background: $wrn-l;
|
||||
}
|
||||
|
||||
.trend-item:last-child {
|
||||
border-radius: 0 0 $r $r;
|
||||
border-bottom: none;
|
||||
.trend-item-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.trend-item-date {
|
||||
@@ -115,8 +150,21 @@
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.trend-item-tag {
|
||||
@include tag($wrn-l, $wrn);
|
||||
}
|
||||
|
||||
.trend-item-warn .trend-item-tag {
|
||||
@include tag($dan-l, $dan);
|
||||
}
|
||||
|
||||
.trend-item-value {
|
||||
font-size: 26px;
|
||||
font-size: 28px;
|
||||
color: $pri;
|
||||
@include serif-number;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.trend-item-value-warn {
|
||||
color: $dan;
|
||||
}
|
||||
|
||||
@@ -33,25 +33,39 @@ export default function Trend() {
|
||||
|
||||
const meta = INDICATOR_META[indicator] || { label: indicator, unit: '' };
|
||||
|
||||
const isOutOfRange = (val: number) => {
|
||||
if (meta.refMin !== undefined && val < meta.refMin) return true;
|
||||
if (meta.refMax !== undefined && val > meta.refMax) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='trend-page'>
|
||||
<View className='trend-header'>
|
||||
<Text className='trend-title'>{meta.label} 趋势</Text>
|
||||
<View className='trend-tabs'>
|
||||
{RANGE_OPTIONS.map((opt) => (
|
||||
<View
|
||||
key={opt.value}
|
||||
className={`trend-tab ${range === opt.value ? 'active' : ''}`}
|
||||
onClick={() => setRange(opt.value)}
|
||||
>
|
||||
<Text className='trend-tab-text'>{opt.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
{/* 页面标题 */}
|
||||
<View className='trend-hero'>
|
||||
<View className='trend-hero-icon'>
|
||||
<Text className='trend-hero-icon-text'>T</Text>
|
||||
</View>
|
||||
<Text className='trend-hero-title'>{meta.label}趋势</Text>
|
||||
</View>
|
||||
|
||||
{/* 时间范围切换 */}
|
||||
<View className='trange-wrap'>
|
||||
{RANGE_OPTIONS.map((opt) => (
|
||||
<View
|
||||
key={opt.value}
|
||||
className={`trange-tab ${range === opt.value ? 'trange-tab-active' : ''}`}
|
||||
onClick={() => setRange(opt.value)}
|
||||
>
|
||||
<Text className={`trange-tab-text ${range === opt.value ? 'trange-tab-text-active' : ''}`}>
|
||||
{opt.label}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* ECharts 折线图 */}
|
||||
<View className='trend-chart-container'>
|
||||
<View className='trend-chart-card'>
|
||||
<TrendChart
|
||||
data={points}
|
||||
referenceMin={meta.refMin}
|
||||
@@ -60,15 +74,36 @@ export default function Trend() {
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 参考区间 */}
|
||||
{meta.refMin !== undefined && meta.refMax !== undefined && (
|
||||
<View className='trend-ref-card'>
|
||||
<Text className='trend-ref-label'>参考区间</Text>
|
||||
<Text className='trend-ref-value'>
|
||||
{meta.refMin} ~ {meta.refMax} {meta.unit}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 数据列表 */}
|
||||
{points.length > 0 && (
|
||||
<View className='trend-list'>
|
||||
{points.slice().reverse().map((p, i) => (
|
||||
<View className='trend-item' key={i}>
|
||||
<Text className='trend-item-date'>{p.date}</Text>
|
||||
<Text className='trend-item-value'>{p.value}{meta.unit ? ` ${meta.unit}` : ''}</Text>
|
||||
</View>
|
||||
))}
|
||||
<Text className='trend-list-title'>历史记录</Text>
|
||||
{points.slice().reverse().map((p, i) => {
|
||||
const abnormal = isOutOfRange(p.value);
|
||||
return (
|
||||
<View className={`trend-item ${abnormal ? 'trend-item-warn' : ''}`} key={i}>
|
||||
<View className='trend-item-left'>
|
||||
<Text className='trend-item-date'>{p.date}</Text>
|
||||
{abnormal && (
|
||||
<Text className='trend-item-tag'>偏高</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text className={`trend-item-value ${abnormal ? 'trend-item-value-warn' : ''}`}>
|
||||
{p.value}{meta.unit ? ` ${meta.unit}` : ''}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -1,177 +1,263 @@
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/mixins.scss';
|
||||
|
||||
.index-page {
|
||||
padding-bottom: 20px;
|
||||
.home-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding-bottom: calc(120px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.greeting-bar {
|
||||
/* ─── 问候区 ─── */
|
||||
.greeting-section {
|
||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||
padding: 40px 32px 60px;
|
||||
color: white;
|
||||
padding: 48px 32px 72px;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.greeting-text {
|
||||
margin-bottom: 8px;
|
||||
.greeting-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.greeting-hello {
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
.greeting-time {
|
||||
font-size: 26px;
|
||||
opacity: 0.85;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.greeting-name {
|
||||
font-size: 36px;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 44px;
|
||||
font-weight: bold;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.greeting-date {
|
||||
font-size: 24px;
|
||||
opacity: 0.8;
|
||||
opacity: 0.7;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.health-card {
|
||||
/* ─── 今日健康 ─── */
|
||||
.health-section {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
margin: -30px 24px 24px;
|
||||
box-shadow: $shadow-md;
|
||||
margin: -36px 24px 24px;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
@include section-title;
|
||||
}
|
||||
|
||||
.health-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.health-item {
|
||||
.health-cell {
|
||||
background: $bg;
|
||||
border-radius: $r-sm;
|
||||
padding: 20px;
|
||||
padding: 20px 16px;
|
||||
text-align: center;
|
||||
border-left: 4px solid transparent;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&.health-item-ok { border-left-color: $acc; }
|
||||
&.health-item-warn { border-left-color: $dan; }
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.health-item-bottom {
|
||||
.health-cell-label {
|
||||
font-size: 22px;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.health-cell-value {
|
||||
@include serif-number;
|
||||
font-size: 44px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.health-cell-bottom {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.health-status {
|
||||
.health-cell-unit {
|
||||
font-size: 20px;
|
||||
|
||||
&.normal { color: $acc; }
|
||||
&.high, &.low { color: $dan; font-weight: bold; }
|
||||
}
|
||||
|
||||
.health-label {
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.health-value {
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
display: block;
|
||||
margin: 8px 0 4px;
|
||||
}
|
||||
|
||||
.health-unit {
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.quick-services {
|
||||
.health-cell-tag {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
padding: 2px 10px;
|
||||
border-radius: $r-sm;
|
||||
display: inline-block;
|
||||
|
||||
&.tag-ok {
|
||||
background: $acc-l;
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
&.tag-warn {
|
||||
background: $wrn-l;
|
||||
color: $wrn;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── 快捷服务 ─── */
|
||||
.services-section {
|
||||
margin: 0 24px 24px;
|
||||
}
|
||||
|
||||
.service-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
.services-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.service-item {
|
||||
.service-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px 0;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 8px;
|
||||
.service-icon-wrap {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
border-radius: $r;
|
||||
background: $pri-l;
|
||||
@include flex-center;
|
||||
}
|
||||
|
||||
.service-icon-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
.service-label {
|
||||
font-size: 24px;
|
||||
font-size: 22px;
|
||||
color: $tx2;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.upcoming {
|
||||
/* ─── 待办事项 ─── */
|
||||
.upcoming-section {
|
||||
margin: 0 24px;
|
||||
}
|
||||
|
||||
.upcoming-empty {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 48px 24px;
|
||||
text-align: center;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.upcoming-empty-text {
|
||||
display: block;
|
||||
font-size: 28px;
|
||||
color: $tx2;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.upcoming-empty-hint {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.upcoming-list {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
overflow: hidden;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.upcoming-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24px 28px;
|
||||
border-bottom: 1px solid $bd;
|
||||
&:last-child { border-bottom: none; }
|
||||
padding: 24px 24px;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: $bd-l;
|
||||
}
|
||||
}
|
||||
|
||||
.upcoming-item-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.upcoming-item-title {
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.upcoming-item-sub {
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.upcoming-item-tag {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
padding: 4px 14px;
|
||||
border-radius: $r-sm;
|
||||
flex-shrink: 0;
|
||||
margin-right: 12px;
|
||||
|
||||
&.tag-ok {
|
||||
background: $acc-l;
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
&.tag-warn {
|
||||
background: $wrn-l;
|
||||
color: $wrn;
|
||||
}
|
||||
|
||||
&.tag-default {
|
||||
background: $bd-l;
|
||||
color: $tx2;
|
||||
}
|
||||
}
|
||||
|
||||
.upcoming-item-arrow {
|
||||
font-size: 36px;
|
||||
color: $tx3;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 26px;
|
||||
font-size: 32px;
|
||||
color: $tx3;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/mixins.scss';
|
||||
|
||||
.login-scroll {
|
||||
height: 100vh;
|
||||
@@ -6,105 +7,132 @@
|
||||
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||
background: $bg;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120px 60px 60px;
|
||||
padding: 160px 56px 80px;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
/* ─── 品牌区 ─── */
|
||||
.login-brand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 120px;
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 30px;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
border-radius: $r-lg;
|
||||
background: $pri;
|
||||
@include flex-center;
|
||||
margin-bottom: 36px;
|
||||
box-shadow: 0 8px 24px rgba($pri, 0.3);
|
||||
}
|
||||
|
||||
.login-logo-text {
|
||||
font-size: 60px;
|
||||
color: white;
|
||||
.login-logo-mark {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 64px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 48px;
|
||||
color: white;
|
||||
color: $tx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
font-size: 28px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 26px;
|
||||
color: $tx2;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
height: 88px;
|
||||
background: white;
|
||||
color: $pri;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
border-radius: $r;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* ─── 装饰线 ─── */
|
||||
.login-divider {
|
||||
width: 48px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
.login-divider-line {
|
||||
height: 3px;
|
||||
background: $pri;
|
||||
border-radius: 2px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* ─── 登录按钮 ─── */
|
||||
.login-body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
height: 96px;
|
||||
background: $pri;
|
||||
color: #fff;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
border-radius: $r;
|
||||
border: none;
|
||||
@include flex-center;
|
||||
letter-spacing: 0.04em;
|
||||
box-shadow: 0 4px 16px rgba($pri, 0.25);
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── 协议 ─── */
|
||||
.agreement-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-top: 32px;
|
||||
margin-top: 40px;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
.agreement-check {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.6);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid $bd;
|
||||
border-radius: $r-sm;
|
||||
@include flex-center;
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.checked {
|
||||
background: $pri;
|
||||
border-color: $pri;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox.checked {
|
||||
background: white;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
.check-mark {
|
||||
font-size: 22px;
|
||||
color: $pri;
|
||||
.agreement-check-mark {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
font-size: 24px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
line-height: 1.6;
|
||||
font-size: 22px;
|
||||
color: $tx2;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ export default function Login() {
|
||||
const [agreed, setAgreed] = useState(false);
|
||||
const { login, bindPhone, loading, isMedicalStaff } = useAuthStore();
|
||||
|
||||
/** 登录/绑定成功后根据角色跳转 */
|
||||
const navigateAfterLogin = () => {
|
||||
if (isMedicalStaff()) {
|
||||
Taro.redirectTo({ url: '/pages/doctor/index' });
|
||||
@@ -59,14 +58,21 @@ export default function Login() {
|
||||
return (
|
||||
<ScrollView scrollY className='login-scroll'>
|
||||
<View className='login-page'>
|
||||
<View className='login-header'>
|
||||
{/* 品牌区 */}
|
||||
<View className='login-brand'>
|
||||
<View className='login-logo'>
|
||||
<Text className='login-logo-text'>+</Text>
|
||||
<Text className='login-logo-mark'>+</Text>
|
||||
</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}>
|
||||
@@ -84,9 +90,10 @@ export default function Login() {
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 协议 */}
|
||||
<View className='agreement-row'>
|
||||
<View className={`checkbox ${agreed ? 'checked' : ''}`} onClick={() => setAgreed(!agreed)}>
|
||||
{agreed && <Text className='check-mark'>✓</Text>}
|
||||
<View className={`agreement-check ${agreed ? 'checked' : ''}`} onClick={() => setAgreed(!agreed)}>
|
||||
{agreed && <Text className='agreement-check-mark'>✓</Text>}
|
||||
</View>
|
||||
<Text className='agreement-text'>
|
||||
我已阅读并同意
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.detail-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
@@ -8,34 +38,34 @@
|
||||
|
||||
/* ===== 余额卡片 ===== */
|
||||
.balance-card {
|
||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||
background: $card;
|
||||
margin: 20px 24px 16px;
|
||||
border-radius: $r-lg;
|
||||
padding: 32px;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
.balance-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.balance-label {
|
||||
font-size: 26px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.balance-value {
|
||||
font-size: 56px;
|
||||
@include serif-number;
|
||||
font-size: 60px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
letter-spacing: 1px;
|
||||
color: $pri;
|
||||
display: block;
|
||||
margin-bottom: 28px;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
.balance-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
background: $bg;
|
||||
border-radius: $r;
|
||||
padding: 20px 0;
|
||||
}
|
||||
@@ -48,47 +78,46 @@
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
@include serif-number;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&.green {
|
||||
color: #A7F3D0;
|
||||
&.stat-earn {
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
&.orange {
|
||||
color: #FDE68A;
|
||||
&.stat-spend {
|
||||
color: $wrn;
|
||||
}
|
||||
|
||||
&.gray {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
&.stat-expired {
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 22px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 1px;
|
||||
height: 48px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: $bd;
|
||||
}
|
||||
|
||||
/* ===== 类型筛选标签 ===== */
|
||||
.type-tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
padding: 20px 24px 0;
|
||||
background: $card;
|
||||
padding: 0 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.type-tab {
|
||||
@include flex-center;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 16px 0;
|
||||
position: relative;
|
||||
|
||||
@@ -98,7 +127,7 @@
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 48px;
|
||||
width: 40px;
|
||||
height: 4px;
|
||||
background: $pri;
|
||||
border-radius: 2px;
|
||||
@@ -107,9 +136,9 @@
|
||||
|
||||
.type-tab-text {
|
||||
font-size: 28px;
|
||||
color: $tx2;
|
||||
color: $tx3;
|
||||
|
||||
&.active {
|
||||
.type-tab.active & {
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -127,45 +156,44 @@
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.tx-icon {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.tx-badge {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: $r;
|
||||
@include flex-center;
|
||||
margin-right: 20px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.type-earn {
|
||||
&.tx-badge-earn {
|
||||
background: $acc-l;
|
||||
}
|
||||
|
||||
&.type-spend {
|
||||
background: $dan-l;
|
||||
&.tx-badge-spend {
|
||||
background: $wrn-l;
|
||||
}
|
||||
|
||||
&.type-expired {
|
||||
&.tx-badge-expired {
|
||||
background: $bd-l;
|
||||
}
|
||||
}
|
||||
|
||||
.tx-icon-text {
|
||||
font-size: 32px;
|
||||
.tx-badge-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
|
||||
.type-earn & {
|
||||
.tx-badge-earn & {
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
.type-spend & {
|
||||
color: $dan;
|
||||
.tx-badge-spend & {
|
||||
color: $wrn;
|
||||
}
|
||||
|
||||
.type-expired & {
|
||||
.tx-badge-expired & {
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
@@ -200,20 +228,22 @@
|
||||
}
|
||||
|
||||
.tx-amount {
|
||||
@include serif-number;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&.positive {
|
||||
&.tx-amount-positive {
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
&.negative {
|
||||
color: $dan;
|
||||
&.tx-amount-negative {
|
||||
color: $tx2;
|
||||
}
|
||||
}
|
||||
|
||||
.tx-remaining {
|
||||
@include serif-number;
|
||||
font-size: 20px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
@@ -13,12 +13,6 @@ const TYPE_TABS = [
|
||||
{ key: 'spend', label: '支出' },
|
||||
];
|
||||
|
||||
const TYPE_ICONS: Record<string, { icon: string; className: string }> = {
|
||||
earn: { icon: '↑', className: 'type-earn' },
|
||||
spend: { icon: '↓', className: 'type-spend' },
|
||||
expired: { icon: '⏰', className: 'type-expired' },
|
||||
};
|
||||
|
||||
export default function PointsDetail() {
|
||||
const [account, setAccount] = useState<PointsAccount | null>(null);
|
||||
const [transactions, setTransactions] = useState<PointsTransaction[]>([]);
|
||||
@@ -48,7 +42,6 @@ export default function PointsDetail() {
|
||||
page_size: 10,
|
||||
});
|
||||
let list = res.data || [];
|
||||
// 前端按类型过滤(后端暂不支持 type 参数)
|
||||
if (type) {
|
||||
list = list.filter((t) => t.type === type);
|
||||
}
|
||||
@@ -99,8 +92,16 @@ export default function PointsDetail() {
|
||||
fetchTransactions(1, key, true);
|
||||
};
|
||||
|
||||
const getTypeConfig = (type: string) => {
|
||||
return TYPE_ICONS[type] || { icon: '?', className: 'type-earn' };
|
||||
const getTypeLabel = (type: string) => {
|
||||
if (type === 'earn') return '收';
|
||||
if (type === 'spend') return '支';
|
||||
return '过';
|
||||
};
|
||||
|
||||
const getTypeClass = (type: string) => {
|
||||
if (type === 'earn') return 'earn';
|
||||
if (type === 'spend') return 'spend';
|
||||
return 'expired';
|
||||
};
|
||||
|
||||
const formatAmount = (tx: PointsTransaction) => {
|
||||
@@ -122,23 +123,21 @@ export default function PointsDetail() {
|
||||
<View className='detail-page'>
|
||||
{/* 余额卡片 */}
|
||||
<View className='balance-card'>
|
||||
<View className='balance-row'>
|
||||
<Text className='balance-label'>当前积分</Text>
|
||||
<Text className='balance-value'>{balance.toLocaleString()}</Text>
|
||||
</View>
|
||||
<Text className='balance-label'>当前积分</Text>
|
||||
<Text className='balance-value'>{balance.toLocaleString()}</Text>
|
||||
<View className='balance-stats'>
|
||||
<View className='stat-item'>
|
||||
<Text className='stat-value green'>{(account?.total_earned ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-value stat-earn'>{(account?.total_earned ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-label'>累计获得</Text>
|
||||
</View>
|
||||
<View className='stat-divider' />
|
||||
<View className='stat-item'>
|
||||
<Text className='stat-value orange'>{(account?.total_spent ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-value stat-spend'>{(account?.total_spent ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-label'>累计消费</Text>
|
||||
</View>
|
||||
<View className='stat-divider' />
|
||||
<View className='stat-item'>
|
||||
<Text className='stat-value gray'>{(account?.total_expired ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-value stat-expired'>{(account?.total_expired ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-label'>已过期</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -152,26 +151,21 @@ export default function PointsDetail() {
|
||||
className={`type-tab ${activeTab === tab.key ? 'active' : ''}`}
|
||||
onClick={() => handleTabChange(tab.key)}
|
||||
>
|
||||
<Text
|
||||
className={`type-tab-text ${activeTab === tab.key ? 'active' : ''}`}
|
||||
>
|
||||
{tab.label}
|
||||
</Text>
|
||||
<Text className='type-tab-text'>{tab.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 交易列表 */}
|
||||
{transactions.length === 0 && !loading ? (
|
||||
<EmptyState icon='📊' text='暂无积分记录' hint='签到或兑换后将显示记录' />
|
||||
<EmptyState icon='' text='暂无积分记录' hint='签到或兑换后将显示记录' />
|
||||
) : (
|
||||
<View className='transaction-list'>
|
||||
{transactions.map((tx) => {
|
||||
const typeCfg = getTypeConfig(tx.type);
|
||||
return (
|
||||
<View className='transaction-item' key={tx.id}>
|
||||
<View className={`tx-icon ${typeCfg.className}`}>
|
||||
<Text className='tx-icon-text'>{typeCfg.icon}</Text>
|
||||
<View className={`tx-badge tx-badge-${getTypeClass(tx.type)}`}>
|
||||
<Text className='tx-badge-text'>{getTypeLabel(tx.type)}</Text>
|
||||
</View>
|
||||
<View className='tx-info'>
|
||||
<Text className='tx-desc'>
|
||||
@@ -180,7 +174,7 @@ export default function PointsDetail() {
|
||||
<Text className='tx-date'>{formatDate(tx.created_at)}</Text>
|
||||
</View>
|
||||
<View className='tx-amount-col'>
|
||||
<Text className={`tx-amount ${tx.type === 'earn' ? 'positive' : 'negative'}`}>
|
||||
<Text className={`tx-amount tx-amount-${tx.type === 'earn' ? 'positive' : 'negative'}`}>
|
||||
{formatAmount(tx)}
|
||||
</Text>
|
||||
<Text className='tx-remaining'>余额 {tx.balance_after.toLocaleString()}</Text>
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.exchange-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
@@ -7,58 +37,69 @@
|
||||
}
|
||||
|
||||
/* ===== 商品预览 ===== */
|
||||
.product-preview {
|
||||
.product-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32px 24px;
|
||||
background: $card;
|
||||
margin-bottom: 16px;
|
||||
margin: 20px 24px 16px;
|
||||
border-radius: $r-lg;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
.product-icon-wrap {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
border-radius: $r;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@include flex-center;
|
||||
margin-right: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.preview-icon {
|
||||
font-size: 64px;
|
||||
.product-icon-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 52px;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.preview-info {
|
||||
.product-meta {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.preview-name {
|
||||
.product-name {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.preview-type {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
.product-type-tag {
|
||||
@include tag($pri-l, $pri-d);
|
||||
}
|
||||
|
||||
/* ===== 兑换详情 ===== */
|
||||
.exchange-detail {
|
||||
background: $card;
|
||||
/* ===== 兑换明细 ===== */
|
||||
.detail-section {
|
||||
padding: 0 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.detail-section-title {
|
||||
@include section-title;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
box-shadow: $shadow-sm;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -66,7 +107,7 @@
|
||||
padding: 24px 0;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:last-child {
|
||||
&.last {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
@@ -77,35 +118,37 @@
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
@include serif-number;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
font-weight: bold;
|
||||
|
||||
&.cost {
|
||||
color: $wrn;
|
||||
font-size: 32px;
|
||||
&.detail-cost {
|
||||
color: $pri;
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
&.sufficient {
|
||||
&.detail-sufficient {
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
&.insufficient {
|
||||
&.detail-insufficient {
|
||||
color: $dan;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 温馨提示 ===== */
|
||||
.exchange-notice {
|
||||
.notice-section {
|
||||
background: $card;
|
||||
padding: 24px;
|
||||
margin: 0 24px;
|
||||
border-radius: $r;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
@include section-title;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@@ -113,7 +156,7 @@
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
line-height: 1.6;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
@@ -128,7 +171,7 @@
|
||||
padding: 16px 24px;
|
||||
padding-bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
background: $card;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0 -2px 12px rgba(45, 42, 38, 0.06);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@@ -143,31 +186,28 @@
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.footer-cost-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.footer-cost-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.footer-cost-num {
|
||||
font-size: 36px;
|
||||
@include serif-number;
|
||||
font-size: 38px;
|
||||
font-weight: bold;
|
||||
color: $wrn;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
.footer-cost-unit {
|
||||
font-size: 22px;
|
||||
color: $tx2;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background: $pri;
|
||||
padding: 20px 48px;
|
||||
border-radius: $r;
|
||||
border-radius: $r-pill;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&.disabled {
|
||||
background: $tx3;
|
||||
opacity: 0.6;
|
||||
background: $bd;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,22 @@ import type { PointsAccount, PointsProduct } from '../../../services/points';
|
||||
import Loading from '../../../components/Loading';
|
||||
import './index.scss';
|
||||
|
||||
const TYPE_ICONS: Record<string, string> = {
|
||||
physical: '📦',
|
||||
service: '🎫',
|
||||
privilege: '👑',
|
||||
const TYPE_INITIAL: Record<string, string> = {
|
||||
physical: '物',
|
||||
service: '券',
|
||||
privilege: '权',
|
||||
};
|
||||
|
||||
const TYPE_LABEL: Record<string, string> = {
|
||||
physical: '实物商品',
|
||||
service: '服务券',
|
||||
privilege: '权益卡',
|
||||
};
|
||||
|
||||
const TYPE_COLOR: Record<string, string> = {
|
||||
physical: '#5B7A5E',
|
||||
service: '#C4623A',
|
||||
privilege: '#8B3E1F',
|
||||
};
|
||||
|
||||
export default function ExchangeConfirm() {
|
||||
@@ -81,7 +93,6 @@ export default function ExchangeConfirm() {
|
||||
const order = await exchangeProduct(product.id);
|
||||
Taro.showToast({ title: '兑换成功', icon: 'success', duration: 2000 });
|
||||
|
||||
// 展示核销码弹窗
|
||||
setTimeout(() => {
|
||||
Taro.showModal({
|
||||
title: '兑换成功',
|
||||
@@ -115,62 +126,62 @@ export default function ExchangeConfirm() {
|
||||
);
|
||||
}
|
||||
|
||||
const productType = product?.product_type || 'physical';
|
||||
const initial = TYPE_INITIAL[productType] || '礼';
|
||||
const typeLabel = TYPE_LABEL[productType] || '商品';
|
||||
const typeColor = TYPE_COLOR[productType] || '#C4623A';
|
||||
|
||||
return (
|
||||
<View className='exchange-page'>
|
||||
{/* 商品信息卡片 */}
|
||||
<View className='product-preview'>
|
||||
{/* 商品预览卡片 */}
|
||||
<View className='product-card'>
|
||||
<View
|
||||
className='preview-image'
|
||||
style={{ backgroundColor: '#0891B2' }}
|
||||
className='product-icon-wrap'
|
||||
style={{ backgroundColor: typeColor }}
|
||||
>
|
||||
<Text className='preview-icon'>
|
||||
{product ? TYPE_ICONS[product.product_type] || '🎁' : '🎁'}
|
||||
</Text>
|
||||
<Text className='product-icon-char'>{initial}</Text>
|
||||
</View>
|
||||
<View className='preview-info'>
|
||||
<Text className='preview-name'>{product?.name || ''}</Text>
|
||||
<Text className='preview-type'>
|
||||
{product?.product_type === 'physical'
|
||||
? '实物商品'
|
||||
: product?.product_type === 'service'
|
||||
? '服务券'
|
||||
: '权益卡'}
|
||||
</Text>
|
||||
<View className='product-meta'>
|
||||
<Text className='product-name'>{product?.name || ''}</Text>
|
||||
<Text className='product-type-tag'>{typeLabel}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 兑换详情 */}
|
||||
<View className='exchange-detail'>
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>所需积分</Text>
|
||||
<Text className='detail-value cost'>{cost.toLocaleString()}</Text>
|
||||
</View>
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>当前余额</Text>
|
||||
<Text
|
||||
className={`detail-value ${insufficient ? 'insufficient' : 'sufficient'}`}
|
||||
>
|
||||
{balance.toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
{insufficient && (
|
||||
{/* 兑换明细 */}
|
||||
<View className='detail-section'>
|
||||
<Text className='detail-section-title'>兑换明细</Text>
|
||||
<View className='detail-card'>
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>差额</Text>
|
||||
<Text className='detail-value insufficient'>
|
||||
-{(cost - balance).toLocaleString()}
|
||||
<Text className='detail-label'>所需积分</Text>
|
||||
<Text className='detail-value detail-cost'>{cost.toLocaleString()}</Text>
|
||||
</View>
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>当前余额</Text>
|
||||
<Text
|
||||
className={`detail-value ${insufficient ? 'detail-insufficient' : 'detail-sufficient'}`}
|
||||
>
|
||||
{balance.toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
{insufficient && (
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>差额</Text>
|
||||
<Text className='detail-value detail-insufficient'>
|
||||
-{(cost - balance).toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<View className='detail-row last'>
|
||||
<Text className='detail-label'>库存</Text>
|
||||
<Text className='detail-value'>
|
||||
{product && product.stock > 0 ? `剩余 ${product.stock} 件` : '已兑完'}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<View className='detail-row'>
|
||||
<Text className='detail-label'>库存</Text>
|
||||
<Text className='detail-value'>
|
||||
{product && product.stock > 0 ? `剩余 ${product.stock} 件` : '已兑完'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 温馨提示 */}
|
||||
<View className='exchange-notice'>
|
||||
<View className='notice-section'>
|
||||
<Text className='notice-title'>温馨提示</Text>
|
||||
<Text className='notice-text'>
|
||||
兑换成功后将生成核销码,请凭核销码到前台核销领取。
|
||||
@@ -182,10 +193,8 @@ export default function ExchangeConfirm() {
|
||||
<View className='exchange-footer'>
|
||||
<View className='footer-cost'>
|
||||
<Text className='footer-cost-label'>合计</Text>
|
||||
<View className='footer-cost-value'>
|
||||
<Text className='footer-cost-icon'>🪙</Text>
|
||||
<Text className='footer-cost-num'>{cost.toLocaleString()}</Text>
|
||||
</View>
|
||||
<Text className='footer-cost-num'>{cost.toLocaleString()}</Text>
|
||||
<Text className='footer-cost-unit'>积分</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`confirm-btn ${insufficient || (product?.stock ?? 0) <= 0 || submitting ? 'disabled' : ''}`}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/mixins.scss';
|
||||
|
||||
.mall-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding-bottom: 40px;
|
||||
padding-bottom: calc(120px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* ===== 积分余额卡片 ===== */
|
||||
/* ─── 积分余额卡片 ─── */
|
||||
.mall-header {
|
||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||
padding: 32px;
|
||||
padding-top: 48px;
|
||||
padding: 48px 32px 36px;
|
||||
}
|
||||
|
||||
.points-card {
|
||||
@@ -20,7 +20,7 @@
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.points-card-top {
|
||||
.points-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -36,9 +36,13 @@
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border: 1px solid rgba(255, 255, 255, 0.4);
|
||||
padding: 10px 28px;
|
||||
border-radius: 32px;
|
||||
border-radius: $r-pill;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&.checked {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
@@ -47,8 +51,8 @@
|
||||
|
||||
.checkin-btn-text {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.checkin-btn.checked .checkin-btn-text {
|
||||
@@ -56,12 +60,14 @@
|
||||
}
|
||||
|
||||
.points-balance {
|
||||
@include serif-number;
|
||||
font-size: 72px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
color: #fff;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 2px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.points-streak {
|
||||
@@ -70,12 +76,10 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ===== 商品类型切换 ===== */
|
||||
/* ─── 商品类型切换 ─── */
|
||||
.type-tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
padding: 20px 24px 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.type-tab {
|
||||
@@ -103,15 +107,15 @@
|
||||
|
||||
&.active {
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 商品网格 ===== */
|
||||
/* ─── 商品网格 ─── */
|
||||
.product-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
gap: 16px;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
@@ -119,19 +123,32 @@
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 200px;
|
||||
@include flex-center;
|
||||
|
||||
&.type-physical { background: $pri-l; }
|
||||
&.type-service { background: $acc-l; }
|
||||
&.type-privilege { background: $wrn-l; }
|
||||
}
|
||||
|
||||
.product-image-icon {
|
||||
font-size: 64px;
|
||||
.product-image-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 56px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
line-height: 1;
|
||||
|
||||
.type-service & { color: $acc; }
|
||||
.type-privilege & { color: $wrn; }
|
||||
}
|
||||
|
||||
.product-info {
|
||||
@@ -140,7 +157,7 @@
|
||||
|
||||
.product-name {
|
||||
font-size: 26px;
|
||||
font-weight: bold;
|
||||
font-weight: 600;
|
||||
color: $tx;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
@@ -161,11 +178,15 @@
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.product-points-icon {
|
||||
.product-points-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: $wrn;
|
||||
}
|
||||
|
||||
.product-points-value {
|
||||
@include serif-number;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $wrn;
|
||||
@@ -174,15 +195,69 @@
|
||||
.product-stock {
|
||||
font-size: 20px;
|
||||
padding: 2px 10px;
|
||||
border-radius: 8px;
|
||||
border-radius: $r-sm;
|
||||
|
||||
&.out {
|
||||
color: $tx3;
|
||||
background: $bd-l;
|
||||
@include tag($bd-l, $tx3);
|
||||
}
|
||||
|
||||
&.low {
|
||||
color: $dan;
|
||||
background: $dan-l;
|
||||
@include tag($dan-l, $dan);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── 空状态 ─── */
|
||||
.mall-empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 160px 40px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
background: $pri-l;
|
||||
@include flex-center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.empty-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 52px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: $tx;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 26px;
|
||||
color: $tx3;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.empty-action {
|
||||
background: $pri;
|
||||
border-radius: $r;
|
||||
padding: 16px 48px;
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-action-text {
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -9,21 +9,20 @@ import {
|
||||
} from '../../services/points';
|
||||
import type { PointsAccount, PointsProduct, CheckinStatus } from '../../services/points';
|
||||
import { useAuthStore } from '../../stores/auth';
|
||||
import EmptyState from '../../components/EmptyState';
|
||||
import Loading from '../../components/Loading';
|
||||
import './index.scss';
|
||||
|
||||
const PRODUCT_TYPE_TABS = [
|
||||
{ key: '', label: '全部' },
|
||||
{ key: 'physical', label: '实物' },
|
||||
{ key: 'service', label: '服务券' },
|
||||
{ key: 'privilege', label: '权益' },
|
||||
{ key: 'physical', label: '实物', char: '物' },
|
||||
{ key: 'service', label: '服务券', char: '券' },
|
||||
{ key: 'privilege', label: '权益', char: '权' },
|
||||
];
|
||||
|
||||
const TYPE_COLORS: Record<string, string> = {
|
||||
physical: '#0891B2',
|
||||
service: '#059669',
|
||||
privilege: '#D97706',
|
||||
const TYPE_BG: Record<string, string> = {
|
||||
physical: 'type-physical',
|
||||
service: 'type-service',
|
||||
privilege: 'type-privilege',
|
||||
};
|
||||
|
||||
export default function Mall() {
|
||||
@@ -117,14 +116,9 @@ export default function Mall() {
|
||||
try {
|
||||
const result = await dailyCheckin();
|
||||
setCheckinStatus(result);
|
||||
// 刷新积分余额
|
||||
const acct = await getAccount();
|
||||
setAccount(acct);
|
||||
Taro.showToast({
|
||||
title: '签到成功',
|
||||
icon: 'success',
|
||||
duration: 2000,
|
||||
});
|
||||
Taro.showToast({ title: '签到成功', icon: 'success', duration: 2000 });
|
||||
} catch (err) {
|
||||
Taro.showToast({
|
||||
title: err instanceof Error ? err.message : '签到失败',
|
||||
@@ -150,40 +144,36 @@ export default function Mall() {
|
||||
|
||||
const balance = account?.balance ?? 0;
|
||||
|
||||
if (noProfile) {
|
||||
return (
|
||||
<View className='mall-page'>
|
||||
<View className='mall-empty-state'>
|
||||
<View className='empty-icon'>
|
||||
<Text className='empty-char'>档</Text>
|
||||
</View>
|
||||
<Text className='empty-title'>请先完善个人档案</Text>
|
||||
<Text className='empty-hint'>建档后即可使用积分商城、签到等功能</Text>
|
||||
<View className='empty-action' onClick={() => Taro.navigateTo({ url: '/pages/profile/family-add/index' })}>
|
||||
<Text className='empty-action-text'>去建档</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='mall-page'>
|
||||
{/* 未关联患者档案时显示引导 */}
|
||||
{noProfile && (
|
||||
<View className='mall-page'>
|
||||
<EmptyState
|
||||
icon='👤'
|
||||
text='请先完善个人档案'
|
||||
hint='建档后即可使用积分商城、签到等功能'
|
||||
actionText='去建档'
|
||||
onAction={() => Taro.navigateTo({ url: '/pages/profile/family-add/index' })}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{!noProfile && (
|
||||
<>
|
||||
{/* 积分余额卡片 */}
|
||||
<View className='mall-header'>
|
||||
<View className='points-card'>
|
||||
<View className='points-card-top'>
|
||||
<View className='points-top'>
|
||||
<Text className='points-label'>当前积分</Text>
|
||||
<View
|
||||
className={`checkin-btn ${
|
||||
checkinStatus?.checked_in_today ? 'checked' : ''
|
||||
}`}
|
||||
className={`checkin-btn ${checkinStatus?.checked_in_today ? 'checked' : ''}`}
|
||||
onClick={handleCheckin}
|
||||
>
|
||||
<Text className='checkin-btn-text'>
|
||||
{checkinLoading
|
||||
? '...'
|
||||
: checkinStatus?.checked_in_today
|
||||
? '已签到'
|
||||
: '签到'}
|
||||
{checkinLoading ? '...' : checkinStatus?.checked_in_today ? '已签到' : '签到'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -204,11 +194,7 @@ export default function Mall() {
|
||||
className={`type-tab ${productType === tab.key ? 'active' : ''}`}
|
||||
onClick={() => handleTabChange(tab.key)}
|
||||
>
|
||||
<Text
|
||||
className={`type-tab-text ${
|
||||
productType === tab.key ? 'active' : ''
|
||||
}`}
|
||||
>
|
||||
<Text className={`type-tab-text ${productType === tab.key ? 'active' : ''}`}>
|
||||
{tab.label}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -217,35 +203,28 @@ export default function Mall() {
|
||||
|
||||
{/* 商品列表 */}
|
||||
{products.length === 0 && !loading ? (
|
||||
<EmptyState
|
||||
icon='🎁'
|
||||
text='暂无商品'
|
||||
hint='更多好物即将上架'
|
||||
/>
|
||||
<View className='mall-empty-state'>
|
||||
<View className='empty-icon'>
|
||||
<Text className='empty-char'>礼</Text>
|
||||
</View>
|
||||
<Text className='empty-title'>暂无商品</Text>
|
||||
<Text className='empty-hint'>更多好物即将上架</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className='product-grid'>
|
||||
{products.map((item) => (
|
||||
<View className='product-card' key={item.id} onClick={() => handleProductClick(item)}>
|
||||
<View
|
||||
className='product-image'
|
||||
style={{ backgroundColor: TYPE_COLORS[item.product_type] || '#94A3B8' }}
|
||||
>
|
||||
<Text className='product-image-icon'>
|
||||
{item.product_type === 'physical'
|
||||
? '📦'
|
||||
: item.product_type === 'service'
|
||||
? '🎫'
|
||||
: '👑'}
|
||||
<View className={`product-image ${TYPE_BG[item.product_type] || ''}`}>
|
||||
<Text className='product-image-char'>
|
||||
{item.product_type === 'physical' ? '物' : item.product_type === 'service' ? '券' : '权'}
|
||||
</Text>
|
||||
</View>
|
||||
<View className='product-info'>
|
||||
<Text className='product-name'>{item.name}</Text>
|
||||
<View className='product-bottom'>
|
||||
<View className='product-points'>
|
||||
<Text className='product-points-icon'>🪙</Text>
|
||||
<Text className='product-points-value'>
|
||||
{item.points_cost}
|
||||
</Text>
|
||||
<Text className='product-points-char'>P</Text>
|
||||
<Text className='product-points-value'>{item.points_cost}</Text>
|
||||
</View>
|
||||
{item.stock <= 0 ? (
|
||||
<Text className='product-stock out'>已兑完</Text>
|
||||
@@ -262,8 +241,6 @@ export default function Mall() {
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.orders-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
@@ -13,11 +43,12 @@
|
||||
padding: 20px 24px 0;
|
||||
background: $card;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 0 0 $r-lg $r-lg;
|
||||
}
|
||||
|
||||
.status-tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
@include flex-center;
|
||||
padding: 16px 0;
|
||||
position: relative;
|
||||
|
||||
@@ -27,7 +58,7 @@
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 48px;
|
||||
width: 40px;
|
||||
height: 4px;
|
||||
background: $pri;
|
||||
border-radius: 2px;
|
||||
@@ -36,9 +67,9 @@
|
||||
|
||||
.status-tab-text {
|
||||
font-size: 28px;
|
||||
color: $tx2;
|
||||
color: $tx3;
|
||||
|
||||
&.active {
|
||||
.status-tab.active & {
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -54,7 +85,7 @@
|
||||
border-radius: $r;
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.order-header {
|
||||
@@ -66,6 +97,7 @@
|
||||
}
|
||||
|
||||
.order-product {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
@@ -75,43 +107,12 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.order-status {
|
||||
.order-status-tag {
|
||||
@include tag(transparent, $tx3);
|
||||
padding: 4px 16px;
|
||||
border-radius: 20px;
|
||||
border-radius: $r-pill;
|
||||
margin-left: 12px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.status-pending {
|
||||
background: $wrn-l;
|
||||
|
||||
.order-status-text {
|
||||
color: $wrn;
|
||||
}
|
||||
}
|
||||
|
||||
&.status-verified {
|
||||
background: $acc-l;
|
||||
|
||||
.order-status-text {
|
||||
color: $acc;
|
||||
}
|
||||
}
|
||||
|
||||
&.status-cancelled {
|
||||
background: $dan-l;
|
||||
|
||||
.order-status-text {
|
||||
color: $dan;
|
||||
}
|
||||
}
|
||||
|
||||
&.status-expired {
|
||||
background: $bd-l;
|
||||
|
||||
.order-status-text {
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.order-status-text {
|
||||
@@ -136,11 +137,12 @@
|
||||
}
|
||||
|
||||
.order-row-value {
|
||||
@include serif-number;
|
||||
font-size: 26px;
|
||||
color: $tx;
|
||||
|
||||
&.cost {
|
||||
color: $wrn;
|
||||
&.order-cost {
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
@@ -158,11 +160,13 @@
|
||||
.qrcode-label {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.qrcode-value {
|
||||
@include serif-number;
|
||||
font-size: 24px;
|
||||
color: $pri;
|
||||
color: $pri-d;
|
||||
font-weight: bold;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -14,11 +14,11 @@ const STATUS_TABS = [
|
||||
{ key: 'expired', label: '已过期' },
|
||||
];
|
||||
|
||||
const STATUS_CONFIG: Record<string, { label: string; className: string }> = {
|
||||
pending: { label: '待核销', className: 'status-pending' },
|
||||
verified: { label: '已核销', className: 'status-verified' },
|
||||
cancelled: { label: '已取消', className: 'status-cancelled' },
|
||||
expired: { label: '已过期', className: 'status-expired' },
|
||||
const STATUS_CONFIG: Record<string, { label: string; tagBg: string; tagColor: string }> = {
|
||||
pending: { label: '待核销', tagBg: '#FFF3E0', tagColor: '#C4873A' },
|
||||
verified: { label: '已核销', tagBg: '#E8F0E8', tagColor: '#5B7A5E' },
|
||||
cancelled: { label: '已取消', tagBg: '#FDEAEA', tagColor: '#B54A4A' },
|
||||
expired: { label: '已过期', tagBg: '#F0EBE5', tagColor: '#A8A29E' },
|
||||
};
|
||||
|
||||
export default function MallOrders() {
|
||||
@@ -40,7 +40,6 @@ export default function MallOrders() {
|
||||
page_size: 10,
|
||||
});
|
||||
let list = res.data || [];
|
||||
// 前端按状态过滤(后端暂不支持 status 参数)
|
||||
if (status) {
|
||||
list = list.filter((o) => o.status === status);
|
||||
}
|
||||
@@ -101,7 +100,7 @@ export default function MallOrders() {
|
||||
};
|
||||
|
||||
const getStatusConfig = (status: string) => {
|
||||
return STATUS_CONFIG[status] || { label: status, className: 'status-pending' };
|
||||
return STATUS_CONFIG[status] || { label: status, tagBg: '#F0EBE5', tagColor: '#A8A29E' };
|
||||
};
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
@@ -120,11 +119,7 @@ export default function MallOrders() {
|
||||
className={`status-tab ${activeTab === tab.key ? 'active' : ''}`}
|
||||
onClick={() => handleTabChange(tab.key)}
|
||||
>
|
||||
<Text
|
||||
className={`status-tab-text ${activeTab === tab.key ? 'active' : ''}`}
|
||||
>
|
||||
{tab.label}
|
||||
</Text>
|
||||
<Text className='status-tab-text'>{tab.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
@@ -132,7 +127,7 @@ export default function MallOrders() {
|
||||
{/* 订单列表 */}
|
||||
{orders.length === 0 && !loading ? (
|
||||
<EmptyState
|
||||
icon='📋'
|
||||
icon=''
|
||||
text='暂无订单'
|
||||
hint='去商城兑换心仪商品吧'
|
||||
actionText='去商城'
|
||||
@@ -146,7 +141,10 @@ export default function MallOrders() {
|
||||
<View className='order-card' key={order.id}>
|
||||
<View className='order-header'>
|
||||
<Text className='order-product'>商品 {order.product_id.slice(0, 8)}</Text>
|
||||
<View className={`order-status ${statusCfg.className}`}>
|
||||
<View
|
||||
className='order-status-tag'
|
||||
style={{ background: statusCfg.tagBg, color: statusCfg.tagColor }}
|
||||
>
|
||||
<Text className='order-status-text'>{statusCfg.label}</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -154,8 +152,8 @@ export default function MallOrders() {
|
||||
<View className='order-body'>
|
||||
<View className='order-row'>
|
||||
<Text className='order-row-label'>消耗积分</Text>
|
||||
<Text className='order-row-value cost'>
|
||||
🪙 {order.points_cost.toLocaleString()}
|
||||
<Text className='order-row-value order-cost'>
|
||||
{order.points_cost.toLocaleString()}
|
||||
</Text>
|
||||
</View>
|
||||
<View className='order-row'>
|
||||
@@ -166,9 +164,9 @@ export default function MallOrders() {
|
||||
</View>
|
||||
{order.status === 'pending' && (
|
||||
<View className='order-qrcode' onClick={() => handleShowQrCode(order.qr_code)}>
|
||||
<Text className='qrcode-label'>核销码: </Text>
|
||||
<Text className='qrcode-label'>核销码</Text>
|
||||
<Text className='qrcode-value'>{order.qr_code}</Text>
|
||||
<Text className='qrcode-tap'>点击查看</Text>
|
||||
<Text className='qrcode-tap'>查看</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.family-add-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 140px;
|
||||
padding: 32px 24px;
|
||||
padding-bottom: 160px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@include section-title;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 8px 28px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
padding: 4px 28px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
@@ -27,10 +47,12 @@
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
flex-shrink: 0;
|
||||
width: 140px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
@@ -43,6 +65,10 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-placeholder {
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.form-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -53,12 +79,17 @@
|
||||
.form-picker-text {
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
margin-right: 8px;
|
||||
margin-right: 10px;
|
||||
|
||||
&.placeholder {
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
.form-picker-arrow {
|
||||
font-size: 32px;
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
@@ -69,14 +100,17 @@
|
||||
background: $pri;
|
||||
padding: 28px;
|
||||
text-align: center;
|
||||
box-shadow: 0 -2px 12px rgba(196, 98, 58, 0.15);
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
color: white;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
@@ -59,12 +59,15 @@ export default function FamilyAdd() {
|
||||
|
||||
return (
|
||||
<View className='family-add-page'>
|
||||
<Text className='page-title'>{editId ? '编辑就诊人' : '添加就诊人'}</Text>
|
||||
|
||||
<View className='form-card'>
|
||||
<View className='form-item'>
|
||||
<Text className='form-label'>姓名</Text>
|
||||
<Input
|
||||
className='form-input'
|
||||
placeholder='请输入姓名'
|
||||
placeholderClass='form-placeholder'
|
||||
value={name}
|
||||
onInput={(e) => setName(e.detail.value)}
|
||||
/>
|
||||
@@ -80,7 +83,7 @@ export default function FamilyAdd() {
|
||||
>
|
||||
<View className='form-picker'>
|
||||
<Text className='form-picker-text'>{RELATION_OPTIONS[relationIdx]}</Text>
|
||||
<Text className='form-picker-arrow'>›</Text>
|
||||
<Text className='form-picker-arrow'>></Text>
|
||||
</View>
|
||||
</Picker>
|
||||
</View>
|
||||
@@ -95,7 +98,7 @@ export default function FamilyAdd() {
|
||||
>
|
||||
<View className='form-picker'>
|
||||
<Text className='form-picker-text'>{GENDER_OPTIONS[genderIdx]}</Text>
|
||||
<Text className='form-picker-arrow'>›</Text>
|
||||
<Text className='form-picker-arrow'>></Text>
|
||||
</View>
|
||||
</Picker>
|
||||
</View>
|
||||
@@ -108,8 +111,10 @@ export default function FamilyAdd() {
|
||||
onChange={(e) => setBirthDate(e.detail.value)}
|
||||
>
|
||||
<View className='form-picker'>
|
||||
<Text className='form-picker-text'>{birthDate || '请选择'}</Text>
|
||||
<Text className='form-picker-arrow'>›</Text>
|
||||
<Text className={`form-picker-text ${!birthDate ? 'placeholder' : ''}`}>
|
||||
{birthDate || '请选择'}
|
||||
</Text>
|
||||
<Text className='form-picker-arrow'>></Text>
|
||||
</View>
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
@@ -1,10 +1,45 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.family-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 140px;
|
||||
padding: 32px 24px;
|
||||
padding-bottom: 160px;
|
||||
}
|
||||
|
||||
.family-page-title {
|
||||
@include section-title;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.family-list {
|
||||
@@ -16,42 +51,74 @@
|
||||
.family-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
border: 2px solid transparent;
|
||||
padding: 24px;
|
||||
box-shadow: $shadow-sm;
|
||||
transition: box-shadow 0.2s;
|
||||
|
||||
&:active {
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: $pri;
|
||||
background: $pri-surface;
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
}
|
||||
|
||||
.family-avatar {
|
||||
@include flex-center;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: $r;
|
||||
background: $pri-l;
|
||||
flex-shrink: 0;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.family-avatar-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: $pri-d;
|
||||
}
|
||||
|
||||
.family-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.family-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.family-name {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.family-current-tag {
|
||||
@include tag($pri, #fff);
|
||||
font-size: 18px;
|
||||
padding: 2px 10px;
|
||||
}
|
||||
|
||||
.family-meta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.family-tag {
|
||||
font-size: 24px;
|
||||
color: $pri;
|
||||
background: $pri-l;
|
||||
.family-relation-tag {
|
||||
@include tag($pri-l, $pri-d);
|
||||
font-size: 20px;
|
||||
padding: 2px 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.family-gender {
|
||||
@@ -59,34 +126,17 @@
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.family-check {
|
||||
font-size: 24px;
|
||||
color: $pri;
|
||||
background: $pri-l;
|
||||
padding: 6px 16px;
|
||||
border-radius: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.family-edit {
|
||||
font-size: 24px;
|
||||
color: $pri;
|
||||
flex-shrink: 0;
|
||||
margin-left: 16px;
|
||||
padding: 6px 16px;
|
||||
border: 1px solid $pri;
|
||||
border-radius: 16px;
|
||||
padding: 8px 20px;
|
||||
border: 1px solid $bd;
|
||||
border-radius: $r-pill;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 120px 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28px;
|
||||
color: $tx3;
|
||||
.family-edit-text {
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.family-add-btn {
|
||||
@@ -97,10 +147,13 @@
|
||||
background: $pri;
|
||||
padding: 28px;
|
||||
text-align: center;
|
||||
box-shadow: 0 -2px 12px rgba(196, 98, 58, 0.15);
|
||||
}
|
||||
|
||||
.family-add-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
color: white;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
@@ -53,8 +53,14 @@ export default function FamilyList() {
|
||||
return '未知';
|
||||
};
|
||||
|
||||
const relationInitial = (relation: string) => {
|
||||
return relation ? relation.charAt(0) : '本';
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='family-page'>
|
||||
<Text className='family-page-title'>就诊人管理</Text>
|
||||
|
||||
<View className='family-list'>
|
||||
{patients.map((p) => {
|
||||
const isActive = currentPatient?.id === p.id;
|
||||
@@ -64,15 +70,25 @@ export default function FamilyList() {
|
||||
key={p.id}
|
||||
onClick={() => handleSelect(p)}
|
||||
>
|
||||
<View className='family-avatar'>
|
||||
<Text className='family-avatar-text'>{relationInitial(p.relation || '本人')}</Text>
|
||||
</View>
|
||||
<View className='family-info'>
|
||||
<Text className='family-name'>{p.name}</Text>
|
||||
<View className='family-name-row'>
|
||||
<Text className='family-name'>{p.name}</Text>
|
||||
{isActive && <Text className='family-current-tag'>当前</Text>}
|
||||
</View>
|
||||
<View className='family-meta'>
|
||||
<Text className='family-tag'>{p.relation || '本人'}</Text>
|
||||
<Text className='family-relation-tag'>{p.relation || '本人'}</Text>
|
||||
<Text className='family-gender'>{genderText(p.gender)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
{isActive && <Text className='family-check'>当前</Text>}
|
||||
<Text className='family-edit' onClick={(e) => { e.stopPropagation(); goToEdit(p); }}>编辑</Text>
|
||||
<View
|
||||
className='family-edit'
|
||||
onClick={(e) => { e.stopPropagation(); goToEdit(p); }}
|
||||
>
|
||||
<Text className='family-edit-text'>编辑</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
@@ -83,7 +99,7 @@ export default function FamilyList() {
|
||||
)}
|
||||
|
||||
<View className='family-add-btn' onClick={goToAdd}>
|
||||
<Text className='family-add-text'>+ 添加就诊人</Text>
|
||||
<Text className='family-add-text'>添加就诊人</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.my-followups-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
@@ -9,35 +24,23 @@
|
||||
display: flex;
|
||||
background: $card;
|
||||
padding: 0;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 24px 0;
|
||||
padding: 24px 0 20px;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 48px;
|
||||
height: 4px;
|
||||
background: $pri;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 28px;
|
||||
color: $tx2;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.tab-item.active & {
|
||||
color: $pri;
|
||||
@@ -45,18 +48,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
.tab-indicator {
|
||||
width: 32px;
|
||||
height: 4px;
|
||||
background: $pri;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.task-list {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.task-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
}
|
||||
|
||||
.task-top {
|
||||
@@ -67,29 +81,25 @@
|
||||
}
|
||||
|
||||
.task-name {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.task-status {
|
||||
font-size: 24px;
|
||||
padding: 4px 16px;
|
||||
border-radius: 20px;
|
||||
@include tag($bd-l, $tx2);
|
||||
|
||||
&.pending {
|
||||
color: $wrn;
|
||||
background: $wrn-l;
|
||||
@include tag($wrn-l, $wrn);
|
||||
}
|
||||
|
||||
&.completed {
|
||||
color: $acc;
|
||||
background: $acc-l;
|
||||
@include tag($acc-l, $acc);
|
||||
}
|
||||
|
||||
&.overdue {
|
||||
color: $dan;
|
||||
background: $dan-l;
|
||||
@include tag($dan-l, $dan);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,29 +114,8 @@
|
||||
}
|
||||
|
||||
.task-due {
|
||||
@include serif-number;
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 120px 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.loading-hint {
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/mixins.scss';
|
||||
|
||||
.profile-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding-bottom: calc(120px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* ─── 用户信息区 ─── */
|
||||
.profile-header {
|
||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||
padding: 60px 32px 40px;
|
||||
@@ -17,75 +20,83 @@
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
@include flex-center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.profile-avatar-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 48px;
|
||||
color: white;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.profile-name {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 34px;
|
||||
color: white;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.profile-phone {
|
||||
font-size: 26px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
/* ===== 积分余额信息 ===== */
|
||||
.profile-points {
|
||||
/* ─── 积分统计 ─── */
|
||||
.profile-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 24px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
margin-top: 28px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||
border-radius: $r;
|
||||
padding: 20px 32px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:active {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.points-info-item {
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.points-info-value {
|
||||
.stat-value {
|
||||
@include serif-number;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
color: #fff;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.points-info-label {
|
||||
.stat-label {
|
||||
font-size: 22px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.points-info-divider {
|
||||
.stat-divider {
|
||||
width: 1px;
|
||||
height: 48px;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
margin: 0 24px;
|
||||
}
|
||||
|
||||
/* ─── 菜单 ─── */
|
||||
.profile-menu {
|
||||
margin: 24px;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
overflow: hidden;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
@@ -93,11 +104,32 @@
|
||||
align-items: center;
|
||||
padding: 28px 24px;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: $bd-l;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 36px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: $r-sm;
|
||||
background: $pri-l;
|
||||
@include flex-center;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu-icon-char {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: $pri;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
@@ -109,32 +141,21 @@
|
||||
.menu-arrow {
|
||||
font-size: 32px;
|
||||
color: $tx3;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu-badge {
|
||||
background: $dan;
|
||||
border-radius: 20px;
|
||||
min-width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 10px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.menu-badge-text {
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* ─── 退出登录 ─── */
|
||||
.profile-logout {
|
||||
margin: 24px;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
text-align: center;
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.logout-text {
|
||||
|
||||
@@ -7,13 +7,13 @@ import type { PointsAccount, CheckinStatus } from '../../services/points';
|
||||
import './index.scss';
|
||||
|
||||
const MENU_ITEMS = [
|
||||
{ label: '我的订单', icon: '🛒', path: '/pages/mall/orders/index' },
|
||||
{ label: '积分明细', icon: '📊', path: '/pages/mall/detail/index' },
|
||||
{ label: '就诊人管理', icon: '👥', path: '/pages/profile/family/index' },
|
||||
{ label: '我的报告', icon: '📋', path: '/pages/profile/reports/index' },
|
||||
{ label: '我的随访', icon: '💬', path: '/pages/profile/followups/index' },
|
||||
{ label: '用药提醒', icon: '💊', path: '/pages/profile/medication/index' },
|
||||
{ label: '设置', icon: '⚙️', path: '/pages/profile/settings/index' },
|
||||
{ label: '我的订单', char: '单', path: '/pages/mall/orders/index' },
|
||||
{ label: '积分明细', char: '明', path: '/pages/mall/detail/index' },
|
||||
{ label: '就诊人管理', char: '人', path: '/pages/profile/family/index' },
|
||||
{ label: '我的报告', char: '报', path: '/pages/profile/reports/index' },
|
||||
{ label: '我的随访', char: '随', path: '/pages/profile/followups/index' },
|
||||
{ label: '用药提醒', char: '药', path: '/pages/profile/medication/index' },
|
||||
{ label: '设置', char: '设', path: '/pages/profile/settings/index' },
|
||||
];
|
||||
|
||||
export default function Profile() {
|
||||
@@ -35,7 +35,7 @@ export default function Profile() {
|
||||
setPointsAccount(acct);
|
||||
setCheckinInfo(status);
|
||||
} catch {
|
||||
// 账户可能尚未创建,静默处理
|
||||
// 账户可能尚未创建
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -56,6 +56,7 @@ export default function Profile() {
|
||||
|
||||
return (
|
||||
<View className='profile-page'>
|
||||
{/* 用户信息区 */}
|
||||
<View className='profile-header'>
|
||||
<View className='profile-avatar'>
|
||||
<Text className='profile-avatar-text'>
|
||||
@@ -65,27 +66,24 @@ export default function Profile() {
|
||||
<Text className='profile-name'>{user?.display_name || '未登录'}</Text>
|
||||
<Text className='profile-phone'>{user?.phone || ''}</Text>
|
||||
|
||||
{/* 积分余额信息 */}
|
||||
{/* 积分余额 */}
|
||||
<View
|
||||
className='profile-points'
|
||||
className='profile-stats'
|
||||
onClick={() => Taro.navigateTo({ url: '/pages/mall/detail/index' })}
|
||||
>
|
||||
<View className='points-info-item'>
|
||||
<Text className='points-info-value'>
|
||||
{(pointsAccount?.balance ?? 0).toLocaleString()}
|
||||
</Text>
|
||||
<Text className='points-info-label'>积分</Text>
|
||||
<View className='stat-item'>
|
||||
<Text className='stat-value'>{(pointsAccount?.balance ?? 0).toLocaleString()}</Text>
|
||||
<Text className='stat-label'>积分</Text>
|
||||
</View>
|
||||
<View className='points-info-divider' />
|
||||
<View className='points-info-item'>
|
||||
<Text className='points-info-value'>
|
||||
{checkinInfo?.consecutive_days ?? 0}
|
||||
</Text>
|
||||
<Text className='points-info-label'>连续打卡(天)</Text>
|
||||
<View className='stat-divider' />
|
||||
<View className='stat-item'>
|
||||
<Text className='stat-value'>{checkinInfo?.consecutive_days ?? 0}</Text>
|
||||
<Text className='stat-label'>连续打卡(天)</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 菜单 */}
|
||||
<View className='profile-menu'>
|
||||
{MENU_ITEMS.map((item) => (
|
||||
<View
|
||||
@@ -93,13 +91,16 @@ export default function Profile() {
|
||||
key={item.label}
|
||||
onClick={() => handleMenuClick(item.path)}
|
||||
>
|
||||
<Text className='menu-icon'>{item.icon}</Text>
|
||||
<View className='menu-icon'>
|
||||
<Text className='menu-icon-char'>{item.char}</Text>
|
||||
</View>
|
||||
<Text className='menu-label'>{item.label}</Text>
|
||||
<Text className='menu-arrow'>›</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 退出登录 */}
|
||||
<View className='profile-logout' onClick={handleLogout}>
|
||||
<Text className='logout-text'>退出登录</Text>
|
||||
</View>
|
||||
|
||||
@@ -1,10 +1,35 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.medication-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 140px;
|
||||
padding: 32px 24px;
|
||||
padding-bottom: 160px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@include section-title;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.reminder-list {
|
||||
@@ -16,19 +41,42 @@
|
||||
.reminder-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
padding: 24px;
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.55;
|
||||
}
|
||||
}
|
||||
|
||||
.reminder-left {
|
||||
.reminder-avatar {
|
||||
@include flex-center;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: $r;
|
||||
background: $acc-l;
|
||||
flex-shrink: 0;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.reminder-avatar-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
.reminder-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.reminder-name {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
@@ -36,6 +84,7 @@
|
||||
}
|
||||
|
||||
.reminder-dosage {
|
||||
@include serif-number;
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
}
|
||||
@@ -44,12 +93,14 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
width: 80px;
|
||||
height: 44px;
|
||||
border-radius: 22px;
|
||||
border-radius: $r-pill;
|
||||
padding: 4px;
|
||||
position: relative;
|
||||
transition: background 0.3s;
|
||||
@@ -67,7 +118,7 @@
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
transition: left 0.3s;
|
||||
@@ -87,31 +138,28 @@
|
||||
padding: 4px 12px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 120px 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
margin-top: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
margin-top: 24px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.form-card-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px 0;
|
||||
padding: 24px 0;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:last-of-type {
|
||||
@@ -123,7 +171,7 @@
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
flex-shrink: 0;
|
||||
width: 140px;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
@@ -136,20 +184,33 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-placeholder {
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.time-picker-wrap {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.time-value {
|
||||
@include serif-number;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.time-modify {
|
||||
font-size: 24px;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-top: 20px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.form-cancel {
|
||||
@@ -175,7 +236,7 @@
|
||||
|
||||
.form-confirm-text {
|
||||
font-size: 28px;
|
||||
color: white;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -187,10 +248,13 @@
|
||||
background: $pri;
|
||||
padding: 28px;
|
||||
text-align: center;
|
||||
box-shadow: 0 -2px 12px rgba(196, 98, 58, 0.15);
|
||||
}
|
||||
|
||||
.add-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
color: white;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
@@ -76,12 +76,21 @@ export default function MedicationReminder() {
|
||||
Taro.showToast({ title: '添加成功', icon: 'success' });
|
||||
};
|
||||
|
||||
const nameInitial = (name: string) => {
|
||||
return name ? name.charAt(0) : '药';
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='medication-page'>
|
||||
<Text className='page-title'>用药提醒</Text>
|
||||
|
||||
<View className='reminder-list'>
|
||||
{reminders.map((r) => (
|
||||
<View className='reminder-card' key={r.id}>
|
||||
<View className='reminder-left'>
|
||||
<View className={`reminder-card ${!r.enabled ? 'disabled' : ''}`} key={r.id}>
|
||||
<View className='reminder-avatar'>
|
||||
<Text className='reminder-avatar-text'>{nameInitial(r.name)}</Text>
|
||||
</View>
|
||||
<View className='reminder-info'>
|
||||
<Text className='reminder-name'>{r.name}</Text>
|
||||
<Text className='reminder-dosage'>
|
||||
{r.dosage} | {r.time}
|
||||
@@ -109,14 +118,15 @@ export default function MedicationReminder() {
|
||||
<EmptyState text='暂无用药提醒' />
|
||||
)}
|
||||
|
||||
{/* 添加表单 */}
|
||||
{showForm && (
|
||||
<View className='form-card'>
|
||||
<Text className='form-card-title'>添加提醒</Text>
|
||||
<View className='form-item'>
|
||||
<Text className='form-label'>药品名称</Text>
|
||||
<Input
|
||||
className='form-input'
|
||||
placeholder='请输入药品名称'
|
||||
placeholderClass='form-placeholder'
|
||||
value={formName}
|
||||
onInput={(e) => setFormName(e.detail.value)}
|
||||
/>
|
||||
@@ -125,7 +135,8 @@ export default function MedicationReminder() {
|
||||
<Text className='form-label'>剂量</Text>
|
||||
<Input
|
||||
className='form-input'
|
||||
placeholder='如:1片、10ml'
|
||||
placeholder='如: 1片、10ml'
|
||||
placeholderClass='form-placeholder'
|
||||
value={formDosage}
|
||||
onInput={(e) => setFormDosage(e.detail.value)}
|
||||
/>
|
||||
@@ -139,7 +150,7 @@ export default function MedicationReminder() {
|
||||
>
|
||||
<View className='time-picker-wrap'>
|
||||
<Text className='time-value'>{formTime}</Text>
|
||||
<Text className='time-arrow'>修改</Text>
|
||||
<Text className='time-modify'>修改</Text>
|
||||
</View>
|
||||
</Picker>
|
||||
</View>
|
||||
@@ -156,7 +167,7 @@ export default function MedicationReminder() {
|
||||
|
||||
{!showForm && (
|
||||
<View className='add-btn' onClick={() => setShowForm(true)}>
|
||||
<Text className='add-text'>+ 添加提醒</Text>
|
||||
<Text className='add-text'>添加提醒</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -1,78 +1,116 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.my-reports-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding: 32px 24px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@include section-title;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.report-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.report-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
}
|
||||
|
||||
.report-card-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.report-type-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.report-avatar {
|
||||
@include flex-center;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: $r-sm;
|
||||
background: $pri-l;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.report-avatar-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $pri-d;
|
||||
}
|
||||
|
||||
.report-type {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.report-status {
|
||||
font-size: 24px;
|
||||
padding: 4px 16px;
|
||||
border-radius: 20px;
|
||||
@include tag($bd-l, $tx2);
|
||||
|
||||
&.normal {
|
||||
color: $acc;
|
||||
background: $acc-l;
|
||||
@include tag($acc-l, $acc);
|
||||
}
|
||||
|
||||
&.abnormal {
|
||||
color: $dan;
|
||||
background: $dan-l;
|
||||
@include tag($dan-l, $dan);
|
||||
}
|
||||
}
|
||||
|
||||
.report-date {
|
||||
@include serif-number;
|
||||
font-size: 26px;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 120px 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28px;
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.loading-hint {
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
padding-left: 72px;
|
||||
}
|
||||
|
||||
@@ -54,30 +54,44 @@ export default function MyReports() {
|
||||
|
||||
const formatStatus = (report: LabReport) => {
|
||||
const indicators = report.indicators;
|
||||
if (!indicators || typeof indicators !== 'object') return '未知';
|
||||
if (!indicators || typeof indicators !== 'object') return 'unknown';
|
||||
const vals = Object.values(indicators) as Array<{ status?: string }>;
|
||||
const hasAbnormal = vals.some((v) => v.status === 'high' || v.status === 'low');
|
||||
return hasAbnormal ? '异常' : '正常';
|
||||
return hasAbnormal ? 'abnormal' : 'normal';
|
||||
};
|
||||
|
||||
const typeInitial = (type: string) => {
|
||||
return type ? type.charAt(0) : '报';
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='my-reports-page'>
|
||||
<Text className='page-title'>检查报告</Text>
|
||||
|
||||
<View className='report-list'>
|
||||
{reports.map((r) => (
|
||||
<View
|
||||
className='report-card'
|
||||
key={r.id}
|
||||
onClick={() => goToDetail(r.id)}
|
||||
>
|
||||
<View className='report-card-top'>
|
||||
<Text className='report-type'>{r.report_type}</Text>
|
||||
<Text className={`report-status ${formatStatus(r) === '正常' ? 'normal' : 'abnormal'}`}>
|
||||
{formatStatus(r)}
|
||||
</Text>
|
||||
{reports.map((r) => {
|
||||
const status = formatStatus(r);
|
||||
return (
|
||||
<View
|
||||
className='report-card'
|
||||
key={r.id}
|
||||
onClick={() => goToDetail(r.id)}
|
||||
>
|
||||
<View className='report-card-top'>
|
||||
<View className='report-type-row'>
|
||||
<View className='report-avatar'>
|
||||
<Text className='report-avatar-text'>{typeInitial(r.report_type)}</Text>
|
||||
</View>
|
||||
<Text className='report-type'>{r.report_type}</Text>
|
||||
</View>
|
||||
<Text className={`report-status ${status}`}>
|
||||
{status === 'normal' ? '正常' : status === 'abnormal' ? '异常' : '未知'}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className='report-date'>{r.report_date}</Text>
|
||||
</View>
|
||||
<Text className='report-date'>{r.report_date}</Text>
|
||||
</View>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{reports.length === 0 && !loading && (
|
||||
|
||||
@@ -1,9 +1,29 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.settings-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@include section-title;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
@@ -11,13 +31,12 @@
|
||||
border-radius: $r;
|
||||
overflow: hidden;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.settings-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28px 24px;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
@@ -30,21 +49,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
.settings-icon {
|
||||
@include flex-center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: $r-sm;
|
||||
background: $pri-l;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.settings-icon-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: $pri-d;
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
flex: 1;
|
||||
font-size: 30px;
|
||||
color: $tx;
|
||||
|
||||
.logout-item & {
|
||||
color: $dan;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.logout-label {
|
||||
color: $dan;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.settings-arrow {
|
||||
font-size: 32px;
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ export default function Settings() {
|
||||
content: '确定要清除本地缓存数据吗?不会影响账号信息。',
|
||||
}).then((res) => {
|
||||
if (res.confirm) {
|
||||
// 保留登录态和核心设置(使用明确的 key 列表,不依赖明文 token)
|
||||
const preservedKeys = ['user', 'current_patient', 'current_patient_id', 'tenant_id', 'wechat_openid'];
|
||||
const preservedData: Record<string, unknown> = {};
|
||||
for (const key of preservedKeys) {
|
||||
@@ -23,11 +22,9 @@ export default function Settings() {
|
||||
|
||||
Taro.clearStorageSync();
|
||||
|
||||
// 恢复非敏感数据
|
||||
for (const [key, val] of Object.entries(preservedData)) {
|
||||
Taro.setStorageSync(key, val);
|
||||
}
|
||||
// 安全存储的 token 由 auth store restore() 在下次页面显示时自动恢复
|
||||
|
||||
Taro.showToast({ title: '缓存已清除', icon: 'success' });
|
||||
}
|
||||
@@ -63,18 +60,29 @@ export default function Settings() {
|
||||
|
||||
return (
|
||||
<View className='settings-page'>
|
||||
<Text className='page-title'>设置</Text>
|
||||
|
||||
<View className='settings-group'>
|
||||
<View className='settings-item' onClick={handleClearCache}>
|
||||
<View className='settings-icon'>
|
||||
<Text className='settings-icon-text'>缓</Text>
|
||||
</View>
|
||||
<Text className='settings-label'>清除缓存</Text>
|
||||
<Text className='settings-arrow'>›</Text>
|
||||
<Text className='settings-arrow'>></Text>
|
||||
</View>
|
||||
<View className='settings-item' onClick={handleAbout}>
|
||||
<View className='settings-icon'>
|
||||
<Text className='settings-icon-text'>关</Text>
|
||||
</View>
|
||||
<Text className='settings-label'>关于我们</Text>
|
||||
<Text className='settings-arrow'>›</Text>
|
||||
<Text className='settings-arrow'>></Text>
|
||||
</View>
|
||||
<View className='settings-item' onClick={handlePrivacy}>
|
||||
<View className='settings-icon'>
|
||||
<Text className='settings-icon-text'>隐</Text>
|
||||
</View>
|
||||
<Text className='settings-label'>隐私政策</Text>
|
||||
<Text className='settings-arrow'>›</Text>
|
||||
<Text className='settings-arrow'>></Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -12,10 +12,16 @@
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 34px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
@@ -53,10 +59,11 @@
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
@@ -91,6 +98,7 @@
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
@include serif-number;
|
||||
}
|
||||
|
||||
.indicator-right {
|
||||
@@ -103,6 +111,7 @@
|
||||
font-size: 22px;
|
||||
color: $tx3;
|
||||
margin-bottom: 4px;
|
||||
@include serif-number;
|
||||
}
|
||||
|
||||
.indicator-status {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
@mixin card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: $shadow-md;
|
||||
padding: 24px;
|
||||
margin: 0 24px 20px;
|
||||
}
|
||||
@@ -18,3 +18,27 @@
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: $r-sm;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
// 医疗清新主题 — 青色主调
|
||||
$pri: #0891B2;
|
||||
$pri-l: #E0F7FA;
|
||||
$pri-d: #065A73;
|
||||
$pri-surface: #ECFEFF;
|
||||
$acc: #059669;
|
||||
$acc-l: #D1FAE5;
|
||||
$bg: #F0FDFA;
|
||||
$card: #FFFFFF;
|
||||
$tx: #134E4A;
|
||||
$tx2: #6B7280;
|
||||
$tx3: #94A3B8;
|
||||
$bd: #E5E7EB;
|
||||
$bd-l: #F3F4F6;
|
||||
$dan: #DC2626;
|
||||
$dan-l: #FEE2E2;
|
||||
$wrn: #D97706;
|
||||
$wrn-l: #FEF3C7;
|
||||
// 温润东方风设计系统 — Warm Eastern Design
|
||||
// 赤土橙 #C4623A 贯穿全场,米底留白呼吸
|
||||
|
||||
// ─── 色彩 ───
|
||||
$pri: #C4623A; // 赤土橙 (accent)
|
||||
$pri-l: #F0DDD4; // 赤土浅
|
||||
$pri-d: #8B3E1F; // 赤土深
|
||||
$pri-surface: #F5F0EB; // 温润米底
|
||||
$acc: #5B7A5E; // 鼠尾草绿 (success)
|
||||
$acc-l: #E8F0E8; // 成功浅
|
||||
$bg: #F5F0EB; // 主背景 (warm cream)
|
||||
$card: #FFFFFF; // 卡片白
|
||||
$surface-alt: #EDE8E2; // 辅助底
|
||||
$tx: #2D2A26; // 主文字 (warm black)
|
||||
$tx2: #7A756E; // 次文字 (warm gray)
|
||||
$tx3: #A8A29E; // 淡文字
|
||||
$bd: #E8E2DC; // 边框
|
||||
$bd-l: #F0EBE5; // 浅边框
|
||||
$dan: #B54A4A; // 危险 (muted red)
|
||||
$dan-l: #FDEAEA; // 危险浅
|
||||
$wrn: #C4873A; // 警告 (warm amber)
|
||||
$wrn-l: #FFF3E0; // 警告浅
|
||||
|
||||
// ─── 圆角 ───
|
||||
$r: 12px;
|
||||
$r-sm: 8px;
|
||||
$r-lg: 16px;
|
||||
$r-pill: 999px;
|
||||
|
||||
// ─── 阴影 ───
|
||||
$shadow-sm: 0 1px 4px rgba(45, 42, 38, 0.04);
|
||||
$shadow-md: 0 2px 12px rgba(45, 42, 38, 0.08);
|
||||
$shadow-lg: 0 8px 32px rgba(45, 42, 38, 0.12);
|
||||
|
||||