Compare commits
111 Commits
4be26592f4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6abf45e7e | ||
|
|
5c5c099fb2 | ||
|
|
a12fe0e8a9 | ||
|
|
3c828bfc4a | ||
|
|
11101ac204 | ||
|
|
28bcdc4208 | ||
|
|
890c132890 | ||
|
|
257ca94a25 | ||
|
|
7b5138a630 | ||
|
|
e8ccee02d5 | ||
|
|
4335f7e144 | ||
|
|
66329852b8 | ||
|
|
085163ec7a | ||
|
|
0c28969c3b | ||
|
|
8490344d69 | ||
|
|
e4b19090b8 | ||
|
|
07217336e7 | ||
|
|
19705e31bd | ||
|
|
3e1413aebc | ||
|
|
36f2ba381a | ||
|
|
a3273ca581 | ||
|
|
f58c60599b | ||
|
|
28dafa9bea | ||
|
|
81c174a902 | ||
|
|
3dac6a9eda | ||
|
|
22b8ac7ac6 | ||
|
|
297a151b0c | ||
|
|
c82f7bda1d | ||
|
|
645ec39e8b | ||
|
|
6d5a711d2c | ||
|
|
786f57c151 | ||
|
|
60dc4dba7a | ||
|
|
85a7dacd16 | ||
|
|
0acf901893 | ||
|
|
a9821ab832 | ||
|
|
1613e3cfe9 | ||
|
|
43f0ba7057 | ||
|
|
5467394ffe | ||
|
|
80ef48a3a3 | ||
|
|
570377a31f | ||
|
|
5fd8e88825 | ||
|
|
4a95a83d6b | ||
|
|
36275eb307 | ||
|
|
263bba264a | ||
|
|
f7bf5a86ea | ||
|
|
d9818c263e | ||
|
|
c452ae81d1 | ||
|
|
a1cbb9fb1d | ||
|
|
a78ee2f154 | ||
|
|
51c41acfa7 | ||
|
|
f668e64266 | ||
|
|
ced93934f1 | ||
|
|
482871301e | ||
|
|
087e23e57b | ||
|
|
741aaf0e40 | ||
|
|
4f84c94a42 | ||
|
|
b1a96ace1f | ||
|
|
e9cfbd108a | ||
|
|
049d230bae | ||
|
|
a62332f1c4 | ||
|
|
1f91dcc5cc | ||
|
|
8a0c9670e6 | ||
|
|
7dac749eff | ||
|
|
0da59c6a0e | ||
|
|
d2512ca9db | ||
|
|
70f69a2008 | ||
|
|
3592b55556 | ||
|
|
2d2e1e191e | ||
|
|
75a70d2e46 | ||
|
|
54116d1a1f | ||
|
|
553de13cd5 | ||
|
|
7fb92714c7 | ||
|
|
3186c5aee9 | ||
|
|
c268229311 | ||
|
|
50b9e8d683 | ||
|
|
a16e86bf04 | ||
|
|
63ff8660fc | ||
|
|
105cae0565 | ||
|
|
37acd34154 | ||
|
|
b728618d61 | ||
|
|
74b1d44068 | ||
|
|
24bb8e7bca | ||
|
|
4d02b2b531 | ||
|
|
93f6e87220 | ||
|
|
84b671d1e5 | ||
|
|
062b4493e4 | ||
|
|
0f55d26076 | ||
|
|
15b5781dbb | ||
|
|
2acd9485c7 | ||
|
|
99dad17eac | ||
|
|
bef2ea7169 | ||
|
|
8d288cadfa | ||
|
|
888fa108ef | ||
|
|
0774dd75ad | ||
|
|
b6838c1bc1 | ||
|
|
438f9ca3f4 | ||
|
|
68ced2bae9 | ||
|
|
3aa436f872 | ||
|
|
2b90db4028 | ||
|
|
95fa09c383 | ||
|
|
0a9272bcf6 | ||
|
|
7e57565ecd | ||
|
|
7b17f94bc0 | ||
|
|
3ff17382ff | ||
|
|
0a5290aee4 | ||
|
|
ef422f354d | ||
|
|
c35ea83799 | ||
|
|
f54fb336dc | ||
|
|
a5b3396adc | ||
|
|
69c3de15f5 | ||
|
|
b235f67c31 |
11
.lintstagedrc.js
Normal file
11
.lintstagedrc.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module.exports = {
|
||||||
|
'*.rs': [
|
||||||
|
'cargo fmt --check --',
|
||||||
|
() => 'cargo clippy -p erp-health -p erp-server -- -D warnings',
|
||||||
|
],
|
||||||
|
'apps/web/src/**/*.{ts,tsx}': (filenames) =>
|
||||||
|
`npx eslint --fix ${filenames.join(' ')}`,
|
||||||
|
'apps/web/src/**/*.test.{ts,tsx}': [
|
||||||
|
'cd apps/web && npx vitest run --reporter=verbose',
|
||||||
|
],
|
||||||
|
};
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1411,10 +1411,12 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"dashmap",
|
||||||
"erp-core",
|
"erp-core",
|
||||||
"futures",
|
"futures",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"hex",
|
"hex",
|
||||||
|
"redis",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ erp-dialysis = { path = "crates/erp-dialysis" }
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
|
dashmap = "6"
|
||||||
|
|
||||||
# Template engine
|
# Template engine
|
||||||
handlebars = "6"
|
handlebars = "6"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export default defineConfig(async (merge) => {
|
|||||||
const baseConfig = {
|
const baseConfig = {
|
||||||
projectName: 'hms-miniprogram',
|
projectName: 'hms-miniprogram',
|
||||||
date: '2026-4-23',
|
date: '2026-4-23',
|
||||||
designWidth: 750,
|
designWidth: 375,
|
||||||
deviceRatio: { 640: 2.34 / 2, 750: 1, 375: 2, 828: 1.81 / 2 },
|
deviceRatio: { 640: 2.34 / 2, 750: 1, 375: 2, 828: 1.81 / 2 },
|
||||||
sourceRoot: 'src',
|
sourceRoot: 'src',
|
||||||
outputRoot: 'dist',
|
outputRoot: 'dist',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"miniprogramRoot": "dist/",
|
"miniprogramRoot": "dist/",
|
||||||
"compileType": "miniprogram",
|
"compileType": "miniprogram",
|
||||||
"setting": {
|
"setting": {
|
||||||
"urlCheck": true,
|
"urlCheck": false,
|
||||||
"automationAudits": true,
|
"automationAudits": true,
|
||||||
"es6": false,
|
"es6": false,
|
||||||
"enhance": false,
|
"enhance": false,
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
"postcss": false,
|
"postcss": false,
|
||||||
"minified": true,
|
"minified": true,
|
||||||
"bundle": false,
|
"bundle": false,
|
||||||
"minifyWXML": true
|
"minifyWXML": true,
|
||||||
}
|
"packNpmManually": false,
|
||||||
|
"packNpmRelationList": [],
|
||||||
|
"ignoreUploadUnusedFiles": true
|
||||||
|
},
|
||||||
|
"condition": {}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
"lazyloadPlaceholderEnable": false,
|
"lazyloadPlaceholderEnable": false,
|
||||||
"skylineRenderEnable": false,
|
"skylineRenderEnable": false,
|
||||||
"preloadBackgroundData": false,
|
"preloadBackgroundData": false,
|
||||||
"autoAudits": false,
|
"autoAudits": true,
|
||||||
"useApiHook": true,
|
"useApiHook": true,
|
||||||
"showShadowRootInWxmlPanel": false,
|
"showShadowRootInWxmlPanel": false,
|
||||||
"useStaticServer": false,
|
"useStaticServer": false,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export default defineAppConfig({
|
|||||||
'dialysis-records/index', 'dialysis-records/detail/index',
|
'dialysis-records/index', 'dialysis-records/detail/index',
|
||||||
'dialysis-prescriptions/index', 'dialysis-prescriptions/detail/index',
|
'dialysis-prescriptions/index', 'dialysis-prescriptions/detail/index',
|
||||||
'consents/index', 'health-records/index', 'diagnoses/index',
|
'consents/index', 'health-records/index', 'diagnoses/index',
|
||||||
|
'elder-mode/index',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -88,5 +89,6 @@ export default defineAppConfig({
|
|||||||
navigationBarBackgroundColor: '#FFFFFF',
|
navigationBarBackgroundColor: '#FFFFFF',
|
||||||
navigationBarTitleText: '健康管理',
|
navigationBarTitleText: '健康管理',
|
||||||
navigationBarTextStyle: 'black',
|
navigationBarTextStyle: 'black',
|
||||||
|
enablePullDownRefresh: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
@import './styles/variables.scss';
|
@import './styles/variables.scss';
|
||||||
|
@import './styles/tokens.scss';
|
||||||
|
@import './styles/elder-mode.scss';
|
||||||
|
|
||||||
page {
|
page {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, 'PingFang SC',
|
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, 'PingFang SC',
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
import { useEffect, PropsWithChildren } from 'react';
|
import { useEffect, PropsWithChildren } from 'react';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro, { useDidShow } from '@tarojs/taro';
|
||||||
import ErrorBoundary from './components/ErrorBoundary';
|
import ErrorBoundary from './components/ErrorBoundary';
|
||||||
import { flushEvents } from './services/analytics';
|
import { flushEvents } from './services/analytics';
|
||||||
import { useAuthStore } from './stores/auth';
|
import { useAuthStore } from './stores/auth';
|
||||||
|
import { useUIStore } from './stores/ui';
|
||||||
import './app.scss';
|
import './app.scss';
|
||||||
|
|
||||||
function App({ children }: PropsWithChildren<Record<string, unknown>>) {
|
function App({ children }: PropsWithChildren<Record<string, unknown>>) {
|
||||||
const restoreAuth = useAuthStore((s) => s.restore);
|
const restoreAuth = useAuthStore((s) => s.restore);
|
||||||
|
const restoreUI = useUIStore((s) => s.restore);
|
||||||
|
|
||||||
|
useDidShow(() => {
|
||||||
|
restoreAuth();
|
||||||
|
restoreUI();
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
restoreAuth();
|
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
flushEvents();
|
flushEvents();
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
box-shadow: $shadow-sm;
|
box-shadow: $shadow-sm;
|
||||||
|
|
||||||
.device-icon {
|
.device-icon {
|
||||||
font-size: 48rpx;
|
font-size: var(--tk-font-h2);
|
||||||
margin-right: 20rpx;
|
margin-right: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,14 +18,14 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
.device-name {
|
.device-name {
|
||||||
font-size: 28rpx;
|
font-size: var(--tk-font-cap);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-status {
|
.device-status {
|
||||||
font-size: 24rpx;
|
font-size: var(--tk-font-micro);
|
||||||
margin-top: 4rpx;
|
margin-top: 4rpx;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.last-sync {
|
.last-sync {
|
||||||
font-size: 22rpx;
|
font-size: var(--tk-font-micro);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
margin-top: 4rpx;
|
margin-top: 4rpx;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -46,6 +46,6 @@
|
|||||||
background: $pri;
|
background: $pri;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-radius: $r-pill;
|
border-radius: $r-pill;
|
||||||
font-size: 24rpx;
|
font-size: var(--tk-font-micro);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,33 @@
|
|||||||
padding: 120px 40px;
|
padding: 120px 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state-icon {
|
.empty-state-icon-wrap {
|
||||||
font-size: 80px;
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: $surface-alt;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-state-icon-char {
|
||||||
|
font-family: Georgia, 'Times New Roman', serif;
|
||||||
|
font-size: var(--tk-font-hero);
|
||||||
|
font-weight: 600;
|
||||||
|
color: $tx3;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-state-text {
|
.empty-state-text {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state-hint {
|
.empty-state-hint {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +45,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.empty-state-action-text {
|
.empty-state-action-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,18 @@ interface EmptyStateProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default React.memo(function EmptyState({
|
export default React.memo(function EmptyState({
|
||||||
icon = '📭',
|
icon,
|
||||||
text,
|
text,
|
||||||
hint,
|
hint,
|
||||||
actionText,
|
actionText,
|
||||||
onAction,
|
onAction,
|
||||||
}: EmptyStateProps) {
|
}: EmptyStateProps) {
|
||||||
|
const displayChar = icon || text.charAt(0);
|
||||||
return (
|
return (
|
||||||
<View className='empty-state'>
|
<View className='empty-state'>
|
||||||
<Text className='empty-state-icon'>{icon}</Text>
|
<View className='empty-state-icon-wrap'>
|
||||||
|
<Text className='empty-state-icon-char'>{displayChar}</Text>
|
||||||
|
</View>
|
||||||
<Text className='empty-state-text'>{text}</Text>
|
<Text className='empty-state-text'>{text}</Text>
|
||||||
{hint && <Text className='empty-state-hint'>{hint}</Text>}
|
{hint && <Text className='empty-state-hint'>{hint}</Text>}
|
||||||
{actionText && onAction && (
|
{actionText && onAction && (
|
||||||
|
|||||||
@@ -23,13 +23,25 @@ export default class ErrorBoundary extends Component<Props, State> {
|
|||||||
console.error('[ErrorBoundary]', error, info.componentStack);
|
console.error('[ErrorBoundary]', error, info.componentStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleRetry = () => {
|
||||||
|
this.setState({ hasError: false });
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
return (
|
return (
|
||||||
<View style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '60vh', padding: '40px' }}>
|
<View style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '60vh', padding: '40px 24px' }}>
|
||||||
<Text style={{ fontSize: '48px', marginBottom: '20px' }}>😵</Text>
|
<View style={{ width: '64px', height: '64px', borderRadius: '32px', background: '#F0DDD4', display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: '20px' }}>
|
||||||
<Text style={{ fontSize: '32px', color: '#134E4A', marginBottom: '12px' }}>页面出了点问题</Text>
|
<Text style={{ fontFamily: 'Georgia, serif', fontSize: '28px', fontWeight: 600, color: '#8B3E1F' }}>!</Text>
|
||||||
<Text style={{ fontSize: '24px', color: '#94A3B8', marginBottom: '24px' }}>请返回重试</Text>
|
</View>
|
||||||
|
<Text style={{ fontSize: '32px', color: '#2D2A26', marginBottom: '12px', fontWeight: 600 }}>页面出了点问题</Text>
|
||||||
|
<Text style={{ fontSize: '24px', color: '#78716C', marginBottom: '32px' }}>请返回重试</Text>
|
||||||
|
<View
|
||||||
|
onClick={this.handleRetry}
|
||||||
|
style={{ background: '#C4623A', borderRadius: '12px', padding: '14px 48px' }}
|
||||||
|
>
|
||||||
|
<Text style={{ color: '#FFFFFF', fontSize: '28px' }}>重新加载</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.error-state-icon {
|
.error-state-icon {
|
||||||
font-size: 80px;
|
font-size: 80px; /* hero icon — kept as-is */
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-state-text {
|
.error-state-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -27,6 +27,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.error-state-retry-text {
|
.error-state-retry-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|||||||
64
apps/miniprogram/src/components/GuestGuard/index.scss
Normal file
64
apps/miniprogram/src/components/GuestGuard/index.scss
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
@import '../../styles/variables.scss';
|
||||||
|
@import '../../styles/mixins.scss';
|
||||||
|
|
||||||
|
.guard-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: $bg;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guard-card {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guard-icon-wrap {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 40px;
|
||||||
|
background: $surface-alt;
|
||||||
|
@include flex-center;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guard-icon {
|
||||||
|
font-size: var(--tk-font-num);
|
||||||
|
color: $tx3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guard-title {
|
||||||
|
font-size: var(--tk-font-body-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
color: $tx;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guard-desc {
|
||||||
|
font-size: var(--tk-font-cap);
|
||||||
|
color: var(--tk-text-secondary);
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guard-btn {
|
||||||
|
display: inline-block;
|
||||||
|
height: 48px;
|
||||||
|
padding: 0 32px;
|
||||||
|
background: $pri;
|
||||||
|
border-radius: $r-pill;
|
||||||
|
@include flex-center;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.guard-btn-text {
|
||||||
|
font-size: var(--tk-font-body-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
28
apps/miniprogram/src/components/GuestGuard/index.tsx
Normal file
28
apps/miniprogram/src/components/GuestGuard/index.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { View, Text } from '@tarojs/components';
|
||||||
|
import { navigateToLogin } from '../../utils/navigate';
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
interface GuestGuardProps {
|
||||||
|
title: string;
|
||||||
|
desc?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GuestGuard({ title, desc }: GuestGuardProps) {
|
||||||
|
return (
|
||||||
|
<View className='guard-page'>
|
||||||
|
<View className='guard-card'>
|
||||||
|
<View className='guard-icon-wrap'>
|
||||||
|
<Text className='guard-icon'>锁</Text>
|
||||||
|
</View>
|
||||||
|
<Text className='guard-title'>{title}</Text>
|
||||||
|
{desc && <Text className='guard-desc'>{desc}</Text>}
|
||||||
|
<View
|
||||||
|
className='guard-btn'
|
||||||
|
onClick={navigateToLogin}
|
||||||
|
>
|
||||||
|
<Text className='guard-btn-text'>立即登录</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -25,6 +25,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.loading-state-text {
|
.loading-state-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,13 @@
|
|||||||
|
|
||||||
.progress-ring-percent {
|
.progress-ring-percent {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-ring-unit {
|
.progress-ring-unit {
|
||||||
font-size: 12px;
|
font-size: var(--tk-font-micro);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: $bd-l;
|
background: $bd-l;
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.step-label {
|
.step-label {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.trend-chart-empty-text {
|
.trend-chart-empty-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.trend-chart-skeleton {
|
.trend-chart-skeleton {
|
||||||
|
|||||||
@@ -14,13 +14,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.week-arrow {
|
.week-arrow {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.week-label {
|
.week-label {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
@@ -39,13 +39,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cell-weekday {
|
.cell-weekday {
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-date {
|
.cell-date {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
|
|||||||
6
apps/miniprogram/src/hooks/useElderClass.ts
Normal file
6
apps/miniprogram/src/hooks/useElderClass.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { useUIStore } from '../stores/ui';
|
||||||
|
|
||||||
|
export function useElderClass(): string {
|
||||||
|
const mode = useUIStore((s) => s.mode);
|
||||||
|
return mode === 'elder' ? 'elder-mode' : '';
|
||||||
|
}
|
||||||
@@ -1,23 +1,5 @@
|
|||||||
@import '../../../styles/variables.scss';
|
@import '../../../styles/variables.scss';
|
||||||
|
@import '../../../styles/mixins.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 {
|
.detail-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -45,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.meta-item {
|
.meta-item {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +45,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@@ -75,7 +57,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -87,7 +69,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.report-content {
|
.report-content {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
@@ -96,8 +78,8 @@
|
|||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 120px 0;
|
padding: 120px 0;
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auto-badge {
|
.auto-badge {
|
||||||
@@ -109,7 +91,7 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 4px 16px;
|
padding: 4px 16px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-body);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
background: #f0e6ff;
|
background: #f0e6ff;
|
||||||
color: #7c3aed;
|
color: #7c3aed;
|
||||||
@@ -124,7 +106,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.trend-tip-text {
|
.trend-tip-text {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: #92400e;
|
color: #92400e;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { View, Text, RichText } from '@tarojs/components';
|
|||||||
import Taro, { useRouter } from '@tarojs/taro';
|
import Taro, { useRouter } from '@tarojs/taro';
|
||||||
import { getAiAnalysisDetail, type AiAnalysisItem } from '@/services/ai-analysis';
|
import { getAiAnalysisDetail, type AiAnalysisItem } from '@/services/ai-analysis';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const TYPE_LABELS: Record<string, string> = {
|
const TYPE_LABELS: Record<string, string> = {
|
||||||
@@ -44,6 +45,7 @@ function markdownToHtml(md: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function AiReportDetail() {
|
export default function AiReportDetail() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const id = router.params.id || '';
|
const id = router.params.id || '';
|
||||||
|
|
||||||
@@ -63,7 +65,7 @@ export default function AiReportDetail() {
|
|||||||
|
|
||||||
if (!analysis) {
|
if (!analysis) {
|
||||||
return (
|
return (
|
||||||
<View className='detail-page'>
|
<View className={`detail-page ${modeClass}`}>
|
||||||
<Text className='empty-text'>报告不存在</Text>
|
<Text className='empty-text'>报告不存在</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -77,7 +79,7 @@ export default function AiReportDetail() {
|
|||||||
const isAutoAnalysis = (analysis.result_metadata as Record<string, unknown>)?.auto_analysis === true;
|
const isAutoAnalysis = (analysis.result_metadata as Record<string, unknown>)?.auto_analysis === true;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='detail-page'>
|
<View className={`detail-page ${modeClass}`}>
|
||||||
<View className='detail-card'>
|
<View className='detail-card'>
|
||||||
<Text className='detail-type'>{TYPE_LABELS[analysis.analysis_type] || analysis.analysis_type}</Text>
|
<Text className='detail-type'>{TYPE_LABELS[analysis.analysis_type] || analysis.analysis_type}</Text>
|
||||||
<View className='detail-meta'>
|
<View className='detail-meta'>
|
||||||
|
|||||||
@@ -1,28 +1,5 @@
|
|||||||
@import '../../../styles/variables.scss';
|
@import '../../../styles/variables.scss';
|
||||||
|
@import '../../../styles/mixins.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 {
|
.ai-report-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -55,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-type {
|
.card-type {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
@@ -87,19 +64,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-time {
|
.card-time {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-model {
|
.card-model {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-more {
|
.no-more {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
padding: 24px 0;
|
padding: 24px 0;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Taro from '@tarojs/taro';
|
|||||||
import { listAiAnalysis, type AiAnalysisItem } from '@/services/ai-analysis';
|
import { listAiAnalysis, type AiAnalysisItem } from '@/services/ai-analysis';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import EmptyState from '@/components/EmptyState';
|
import EmptyState from '@/components/EmptyState';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const TYPE_LABELS: Record<string, string> = {
|
const TYPE_LABELS: Record<string, string> = {
|
||||||
@@ -21,6 +22,7 @@ const STATUS_MAP: Record<string, { text: string; className: string }> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function AiReportList() {
|
export default function AiReportList() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [list, setList] = useState<AiAnalysisItem[]>([]);
|
const [list, setList] = useState<AiAnalysisItem[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
@@ -60,14 +62,14 @@ export default function AiReportList() {
|
|||||||
|
|
||||||
if (list.length === 0) {
|
if (list.length === 0) {
|
||||||
return (
|
return (
|
||||||
<View className='ai-report-page'>
|
<View className={`ai-report-page ${modeClass}`}>
|
||||||
<EmptyState text='暂无 AI 分析报告' />
|
<EmptyState text='暂无 AI 分析报告' />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='ai-report-page'>
|
<View className={`ai-report-page ${modeClass}`}>
|
||||||
<View className='page-title'>AI 分析报告</View>
|
<View className='page-title'>AI 分析报告</View>
|
||||||
<ScrollView scrollY className='report-scroll' onScrollToLower={loadMore}>
|
<ScrollView scrollY className='report-scroll' onScrollToLower={loadMore}>
|
||||||
{list.map((item) => {
|
{list.map((item) => {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
.dept-initial-text {
|
.dept-initial-text {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dept-label {
|
.dept-label {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
|
|
||||||
.slot-section-title {
|
.slot-section-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@@ -114,14 +114,14 @@
|
|||||||
|
|
||||||
.slot-time {
|
.slot-time {
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slot-count {
|
.slot-count {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
|
|
||||||
.confirm-icon-serif {
|
.confirm-icon-serif {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
}
|
}
|
||||||
@@ -169,12 +169,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.confirm-label {
|
.confirm-label {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirm-value {
|
.confirm-value {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.confirm-dept-text {
|
.confirm-dept-text {
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-body);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@
|
|||||||
|
|
||||||
.doctor-avatar-text {
|
.doctor-avatar-text {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 32px;
|
font-size: var(--tk-font-num);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
@@ -238,18 +238,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.doctor-name {
|
.doctor-name {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.doctor-title {
|
.doctor-title {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.doctor-specialty {
|
.doctor-specialty {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.doctor-check-text {
|
.doctor-check-text {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
@@ -274,7 +274,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-label {
|
.form-label {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -284,7 +284,7 @@
|
|||||||
background: $card;
|
background: $card;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
padding: 24px 28px;
|
padding: 24px 28px;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -298,8 +298,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.empty-text {
|
.empty-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 底部操作栏 */
|
/* 底部操作栏 */
|
||||||
@@ -339,7 +339,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-text {
|
.btn-text {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { TEMPLATE_IDS } from '@/services/wechat-templates';
|
|||||||
import { trackEvent } from '@/services/analytics';
|
import { trackEvent } from '@/services/analytics';
|
||||||
import StepIndicator from '../../../components/StepIndicator';
|
import StepIndicator from '../../../components/StepIndicator';
|
||||||
import WeekCalendar from '../../../components/WeekCalendar';
|
import WeekCalendar from '../../../components/WeekCalendar';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const DEPARTMENTS = [
|
const DEPARTMENTS = [
|
||||||
@@ -44,6 +45,7 @@ export default function AppointmentCreate() {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [schedules, setSchedules] = useState<any[]>([]);
|
const [schedules, setSchedules] = useState<any[]>([]);
|
||||||
const [timeSlots, setTimeSlots] = useState<TimeSlot[]>([]);
|
const [timeSlots, setTimeSlots] = useState<TimeSlot[]>([]);
|
||||||
|
const modeClass = useElderClass();
|
||||||
|
|
||||||
const currentPatient = useAuthStore((s) => s.currentPatient);
|
const currentPatient = useAuthStore((s) => s.currentPatient);
|
||||||
|
|
||||||
@@ -148,7 +150,7 @@ export default function AppointmentCreate() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='create-page'>
|
<View className={`create-page ${modeClass}`}>
|
||||||
<StepIndicator
|
<StepIndicator
|
||||||
steps={[{ label: '选科室' }, { label: '选医生' }, { label: '选时段' }]}
|
steps={[{ label: '选科室' }, { label: '选医生' }, { label: '选时段' }]}
|
||||||
current={currentStep}
|
current={currentStep}
|
||||||
|
|||||||
@@ -23,14 +23,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.back-text {
|
.back-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-title {
|
.header-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 34px;
|
font-size: var(--tk-font-num-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
@@ -80,19 +80,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status-tag-text {
|
.status-tag-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-doctor {
|
.status-doctor {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 36px;
|
font-size: var(--tk-font-num-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-dept {
|
.status-dept {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
|
|
||||||
.info-icon-serif {
|
.info-icon-serif {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-body);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
background: $pri-l;
|
background: $pri-l;
|
||||||
width: 36px;
|
width: 36px;
|
||||||
@@ -143,12 +143,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-label {
|
.info-label {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-value {
|
.info-value {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -163,8 +163,8 @@
|
|||||||
|
|
||||||
.info-id {
|
.info-id {
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
|
|
||||||
.tips-title {
|
.tips-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $wrn;
|
color: $wrn;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tips-text {
|
.tips-text {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cancel-text {
|
.cancel-text {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $dan;
|
color: $dan;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { getAppointment, cancelAppointment } from '../../../services/appointment
|
|||||||
import type { Appointment } from '../../../services/appointment';
|
import type { Appointment } from '../../../services/appointment';
|
||||||
import Loading from '../../../components/Loading';
|
import Loading from '../../../components/Loading';
|
||||||
import ErrorState from '../../../components/ErrorState';
|
import ErrorState from '../../../components/ErrorState';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const STATUS_MAP: Record<string, { label: string; className: string }> = {
|
const STATUS_MAP: Record<string, { label: string; className: string }> = {
|
||||||
@@ -22,6 +23,7 @@ export default function AppointmentDetail() {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [cancelling, setCancelling] = useState(false);
|
const [cancelling, setCancelling] = useState(false);
|
||||||
|
const modeClass = useElderClass();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
@@ -65,7 +67,7 @@ export default function AppointmentDetail() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<View className='detail-page'>
|
<View className={`detail-page ${modeClass}`}>
|
||||||
<View className='detail-header'>
|
<View className='detail-header'>
|
||||||
<View className='back-btn' onClick={goBack}><Text className='back-text'>返回</Text></View>
|
<View className='back-btn' onClick={goBack}><Text className='back-text'>返回</Text></View>
|
||||||
<Text className='header-title'>预约详情</Text>
|
<Text className='header-title'>预约详情</Text>
|
||||||
@@ -78,7 +80,7 @@ export default function AppointmentDetail() {
|
|||||||
|
|
||||||
if (error || !appointment) {
|
if (error || !appointment) {
|
||||||
return (
|
return (
|
||||||
<View className='detail-page'>
|
<View className={`detail-page ${modeClass}`}>
|
||||||
<View className='detail-header'>
|
<View className='detail-header'>
|
||||||
<View className='back-btn' onClick={goBack}><Text className='back-text'>返回</Text></View>
|
<View className='back-btn' onClick={goBack}><Text className='back-text'>返回</Text></View>
|
||||||
<Text className='header-title'>预约详情</Text>
|
<Text className='header-title'>预约详情</Text>
|
||||||
@@ -90,7 +92,7 @@ export default function AppointmentDetail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='detail-page'>
|
<View className={`detail-page ${modeClass}`}>
|
||||||
<View className='detail-header'>
|
<View className='detail-header'>
|
||||||
<View className='back-btn' onClick={goBack}><Text className='back-text'>返回</Text></View>
|
<View className='back-btn' onClick={goBack}><Text className='back-text'>返回</Text></View>
|
||||||
<Text className='header-title'>预约详情</Text>
|
<Text className='header-title'>预约详情</Text>
|
||||||
|
|||||||
@@ -17,13 +17,13 @@
|
|||||||
.page-title {
|
.page-title {
|
||||||
@include section-title;
|
@include section-title;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
font-size: 36px;
|
font-size: var(--tk-font-num-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-subtitle {
|
.page-subtitle {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
|
|
||||||
.dept-initial-text {
|
.dept-initial-text {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 32px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.doctor-name {
|
.doctor-name {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dept-tag-text {
|
.dept-tag-text {
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-body);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status-tag-text {
|
.status-tag-text {
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-body);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,12 +157,12 @@
|
|||||||
|
|
||||||
.info-icon-serif {
|
.info-icon-serif {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-text {
|
.info-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fab-text {
|
.fab-text {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { listAppointments } from '../../services/appointment';
|
|||||||
import type { Appointment } from '../../services/appointment';
|
import type { Appointment } from '../../services/appointment';
|
||||||
import EmptyState from '../../components/EmptyState';
|
import EmptyState from '../../components/EmptyState';
|
||||||
import Loading from '../../components/Loading';
|
import Loading from '../../components/Loading';
|
||||||
|
import { useElderClass } from '../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const STATUS_MAP: Record<string, { label: string; className: string }> = {
|
const STATUS_MAP: Record<string, { label: string; className: string }> = {
|
||||||
@@ -30,6 +31,7 @@ export default function AppointmentList() {
|
|||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const loadingRef = useRef(false);
|
const loadingRef = useRef(false);
|
||||||
|
const modeClass = useElderClass();
|
||||||
|
|
||||||
const fetchData = useCallback(async (pageNum: number, isRefresh = false) => {
|
const fetchData = useCallback(async (pageNum: number, isRefresh = false) => {
|
||||||
if (loadingRef.current) return;
|
if (loadingRef.current) return;
|
||||||
@@ -86,11 +88,10 @@ export default function AppointmentList() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='appointment-page'>
|
<View className={`appointment-page ${modeClass}`}>
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
<View className='page-header'>
|
<View className='page-header'>
|
||||||
<Text className='page-title'>预约挂号</Text>
|
<Text className='page-title'>预约挂号</Text>
|
||||||
<Text className='page-subtitle'>Appointment</Text>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 预约列表 */}
|
{/* 预约列表 */}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.article-title {
|
.article-title {
|
||||||
font-size: 38px;
|
font-size: var(--tk-font-hero);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.article-category {
|
.article-category {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
background: $pri-l;
|
background: $pri-l;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
@@ -37,13 +37,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.article-author {
|
.article-author {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.article-date {
|
.article-date {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.article-summary {
|
.article-summary {
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.summary-text {
|
.summary-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@@ -93,6 +93,6 @@
|
|||||||
|
|
||||||
.loading-text,
|
.loading-text,
|
||||||
.empty-text {
|
.empty-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import { View, Text, RichText } from '@tarojs/components';
|
|||||||
import Taro, { useRouter, useShareAppMessage } from '@tarojs/taro';
|
import Taro, { useRouter, useShareAppMessage } from '@tarojs/taro';
|
||||||
import { getArticleDetail, Article } from '../../../services/article';
|
import { getArticleDetail, Article } from '../../../services/article';
|
||||||
import { trackEvent } from '@/services/analytics';
|
import { trackEvent } from '@/services/analytics';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
export default function ArticleDetail() {
|
export default function ArticleDetail() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const id = router.params.id || '';
|
const id = router.params.id || '';
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ export default function ArticleDetail() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<View className='article-detail-page'>
|
<View className={`article-detail-page ${modeClass}`}>
|
||||||
<View className='loading-state'>
|
<View className='loading-state'>
|
||||||
<Text className='loading-text'>加载中...</Text>
|
<Text className='loading-text'>加载中...</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -41,7 +43,7 @@ export default function ArticleDetail() {
|
|||||||
|
|
||||||
if (!article) {
|
if (!article) {
|
||||||
return (
|
return (
|
||||||
<View className='article-detail-page'>
|
<View className={`article-detail-page ${modeClass}`}>
|
||||||
<View className='empty-state'>
|
<View className='empty-state'>
|
||||||
<Text className='empty-text'>文章不存在</Text>
|
<Text className='empty-text'>文章不存在</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -50,7 +52,7 @@ export default function ArticleDetail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='article-detail-page'>
|
<View className={`article-detail-page ${modeClass}`}>
|
||||||
{/* 文章头部 */}
|
{/* 文章头部 */}
|
||||||
<View className='article-header'>
|
<View className='article-header'>
|
||||||
<Text className='article-title'>{article.title}</Text>
|
<Text className='article-title'>{article.title}</Text>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 12px 28px;
|
padding: 12px 28px;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
background: $card;
|
background: $card;
|
||||||
border-radius: 32px;
|
border-radius: 32px;
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.article-card-title {
|
.article-card-title {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.article-card-summary {
|
.article-card-summary {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.article-card-tag {
|
.article-card-tag {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
background: $pri-l;
|
background: $pri-l;
|
||||||
padding: 2px 12px;
|
padding: 2px 12px;
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.article-card-date {
|
.article-card-date {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,8 +116,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.empty-text {
|
.empty-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-hint {
|
.loading-hint {
|
||||||
@@ -126,6 +126,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.loading-text {
|
.loading-text {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/ta
|
|||||||
import { listArticles, listCategories, Article, ArticleCategory } from '../../services/article';
|
import { listArticles, listCategories, Article, ArticleCategory } from '../../services/article';
|
||||||
import EmptyState from '../../components/EmptyState';
|
import EmptyState from '../../components/EmptyState';
|
||||||
import Loading from '../../components/Loading';
|
import Loading from '../../components/Loading';
|
||||||
|
import { useElderClass } from '../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
export default function ArticleList() {
|
export default function ArticleList() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [articles, setArticles] = useState<Article[]>([]);
|
const [articles, setArticles] = useState<Article[]>([]);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
@@ -72,7 +74,7 @@ export default function ArticleList() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='article-page'>
|
<View className={`article-page ${modeClass}`}>
|
||||||
{/* 分类筛选 */}
|
{/* 分类筛选 */}
|
||||||
{categories.length > 0 && (
|
{categories.length > 0 && (
|
||||||
<ScrollView scrollX className='article-categories'>
|
<ScrollView scrollX className='article-categories'>
|
||||||
|
|||||||
@@ -8,61 +8,108 @@
|
|||||||
background: $bg;
|
background: $bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── 导航栏 ─── */
|
||||||
.chat-header {
|
.chat-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 24px 32px;
|
padding: 12px 16px;
|
||||||
background: $card;
|
background: $card;
|
||||||
border-bottom: 1px solid $bd;
|
border-bottom: 1px solid $bd-l;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
&__title {
|
.chat-header__back {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
position: absolute;
|
||||||
font-size: 30px;
|
left: 16px;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header__back-text {
|
||||||
|
font-size: var(--tk-font-body-sm);
|
||||||
|
color: $pri;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header__center {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header__title {
|
||||||
|
font-size: var(--tk-font-body-sm);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__status {
|
.chat-header__status {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-micro);
|
||||||
|
color: $acc;
|
||||||
|
margin-top: 2px;
|
||||||
|
|
||||||
|
&--closed {
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── 消息区 ─── */
|
||||||
.chat-messages {
|
.chat-messages {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 24px;
|
padding: 16px 16px 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-row {
|
.msg-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 16px;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
&--self {
|
&--self {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── 医生头像 ─── */
|
||||||
|
.msg-avatar {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: $pri-l;
|
||||||
|
@include flex-center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-avatar-char {
|
||||||
|
@include serif-number;
|
||||||
|
font-size: var(--tk-font-cap);
|
||||||
|
font-weight: 700;
|
||||||
|
color: $pri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── 消息气泡 ─── */
|
||||||
.msg-bubble {
|
.msg-bubble {
|
||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
padding: 20px 24px;
|
padding: 12px 16px;
|
||||||
border-radius: $r-lg;
|
box-shadow: $shadow-sm;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&--other {
|
&--other {
|
||||||
background: $card;
|
background: $card;
|
||||||
border-top-left-radius: $r-sm;
|
border-radius: $r $r $r $r-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--self {
|
&--self {
|
||||||
background: $pri;
|
background: $pri;
|
||||||
border-top-right-radius: $r-sm;
|
border-radius: $r $r $r-xs $r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-text {
|
.msg-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
@@ -76,88 +123,95 @@
|
|||||||
.msg-date-divider {
|
.msg-date-divider {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 16px 0 12px;
|
padding: 12px 0;
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-micro);
|
||||||
color: #94A3B8;
|
color: var(--tk-text-secondary);
|
||||||
background: #F1F5F9;
|
background: $surface-alt;
|
||||||
padding: 4px 16px;
|
padding: 2px 12px;
|
||||||
border-radius: 8px;
|
border-radius: $r-pill;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-image {
|
.msg-image {
|
||||||
width: 320px;
|
width: 200px;
|
||||||
border-radius: 12px;
|
border-radius: $r-sm;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-time {
|
.msg-time {
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-micro);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 8px;
|
margin-top: 4px;
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
.msg-bubble--self & {
|
.msg-bubble--self & {
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-empty {
|
.chat-empty {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 120px 32px;
|
padding: 80px 24px;
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── 输入栏 ─── */
|
||||||
.chat-input-bar {
|
.chat-input-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16px 24px;
|
gap: 10px;
|
||||||
|
padding: 10px 16px 38px;
|
||||||
background: $card;
|
background: $card;
|
||||||
border-top: 1px solid $bd;
|
border-top: 1px solid $bd-l;
|
||||||
padding-bottom: calc(16px + env(safe-area-inset-bottom));
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input {
|
.chat-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
background: $bg;
|
background: $bg;
|
||||||
border-radius: $r;
|
border: 1.5px solid $bd;
|
||||||
padding: 16px 20px;
|
border-radius: 20px;
|
||||||
font-size: 28px;
|
padding: 0 14px;
|
||||||
margin-right: 16px;
|
font-size: var(--tk-font-cap);
|
||||||
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-send-btn {
|
.chat-send-btn {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 20px;
|
||||||
background: $pri;
|
background: $pri;
|
||||||
border-radius: $r;
|
@include flex-center;
|
||||||
padding: 16px 28px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
box-shadow: 0 2px 6px rgba(196, 98, 58, 0.3);
|
||||||
|
|
||||||
&--disabled {
|
&--disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__text {
|
|
||||||
font-size: 28px;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-send-btn__icon {
|
||||||
|
font-size: var(--tk-font-cap);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-closed-bar {
|
.chat-closed-bar {
|
||||||
padding: 24px;
|
padding: 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: $card;
|
background: $card;
|
||||||
border-top: 1px solid $bd;
|
border-top: 1px solid $bd-l;
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import {
|
|||||||
listMessages,
|
listMessages,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
markSessionRead,
|
markSessionRead,
|
||||||
|
pollMessages,
|
||||||
type ConsultationSession,
|
type ConsultationSession,
|
||||||
type ConsultationMessage,
|
type ConsultationMessage,
|
||||||
} from '@/services/consultation';
|
} from '@/services/consultation';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '@/hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const POLL_INTERVAL = 8000;
|
|
||||||
|
|
||||||
export default function ConsultationDetail() {
|
export default function ConsultationDetail() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const sessionId = router.params.id || '';
|
const sessionId = router.params.id || '';
|
||||||
@@ -23,43 +23,35 @@ export default function ConsultationDetail() {
|
|||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const scrollViewRef = useRef('');
|
const scrollViewRef = useRef('');
|
||||||
const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
const pollingRef = useRef(false);
|
||||||
|
const modeClass = useElderClass();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
loadData();
|
loadData();
|
||||||
markRead();
|
markRead();
|
||||||
startPolling();
|
startLongPolling();
|
||||||
}
|
}
|
||||||
return () => stopPolling();
|
return () => { pollingRef.current = false; };
|
||||||
}, [sessionId]);
|
}, [sessionId]);
|
||||||
|
|
||||||
const startPolling = () => {
|
useEffect(() => {
|
||||||
stopPolling();
|
if (session?.status === 'closed') {
|
||||||
pollTimerRef.current = setInterval(pollNewMessages, POLL_INTERVAL);
|
pollingRef.current = false;
|
||||||
|
}
|
||||||
|
}, [session?.status]);
|
||||||
|
|
||||||
|
const startLongPolling = () => {
|
||||||
|
pollingRef.current = true;
|
||||||
|
longPoll();
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopPolling = () => {
|
const longPoll = async () => {
|
||||||
if (pollTimerRef.current) {
|
if (!pollingRef.current) return;
|
||||||
clearInterval(pollTimerRef.current);
|
|
||||||
pollTimerRef.current = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const pollNewMessages = async () => {
|
|
||||||
if (!session || session.status === 'closed') {
|
|
||||||
stopPolling();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const lastId = messages.length > 0 ? messages[messages.length - 1].id : undefined;
|
const lastId = messages.length > 0 ? messages[messages.length - 1].id : undefined;
|
||||||
const m = await listMessages(sessionId, {
|
const newMsgs = await pollMessages(sessionId, lastId);
|
||||||
page: 1,
|
if (newMsgs && newMsgs.length > 0) {
|
||||||
page_size: 50,
|
|
||||||
after_id: lastId,
|
|
||||||
});
|
|
||||||
const newMsgs = m.data || [];
|
|
||||||
if (newMsgs.length > 0) {
|
|
||||||
setMessages((prev) => {
|
setMessages((prev) => {
|
||||||
const existing = new Set(prev.map((msg) => msg.id));
|
const existing = new Set(prev.map((msg) => msg.id));
|
||||||
const fresh = newMsgs.filter((msg) => !existing.has(msg.id));
|
const fresh = newMsgs.filter((msg) => !existing.has(msg.id));
|
||||||
@@ -67,7 +59,12 @@ export default function ConsultationDetail() {
|
|||||||
});
|
});
|
||||||
scrollViewRef.current = `msg-${messages.length + newMsgs.length}`;
|
scrollViewRef.current = `msg-${messages.length + newMsgs.length}`;
|
||||||
}
|
}
|
||||||
} catch { /* 轮询失败静默忽略 */ }
|
} catch {
|
||||||
|
// 超时或网络错误,静默重试
|
||||||
|
}
|
||||||
|
if (pollingRef.current) {
|
||||||
|
longPoll();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -80,7 +77,7 @@ export default function ConsultationDetail() {
|
|||||||
setSession(s);
|
setSession(s);
|
||||||
setMessages(m.data || []);
|
setMessages(m.data || []);
|
||||||
scrollViewRef.current = `msg-${(m.data || []).length}`;
|
scrollViewRef.current = `msg-${(m.data || []).length}`;
|
||||||
if (s.status === 'closed') stopPolling();
|
if (s.status === 'closed') pollingRef.current = false;
|
||||||
} catch {
|
} catch {
|
||||||
Taro.showToast({ title: '加载失败', icon: 'none' });
|
Taro.showToast({ title: '加载失败', icon: 'none' });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -137,14 +134,24 @@ export default function ConsultationDetail() {
|
|||||||
if (loading) return <Loading />;
|
if (loading) return <Loading />;
|
||||||
|
|
||||||
const isOpen = session?.status !== 'closed';
|
const isOpen = session?.status !== 'closed';
|
||||||
|
const doctorInitial = (session?.subject || '医').charAt(0);
|
||||||
|
const statusLabel = session?.status === 'active' ? '进行中'
|
||||||
|
: session?.status === 'pending' ? '等待接诊'
|
||||||
|
: '已结束';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='chat-page'>
|
<View className={`chat-page ${modeClass}`}>
|
||||||
|
{/* 导航栏 — 对齐设计稿:返回 + 标题 + 副标题 */}
|
||||||
<View className='chat-header'>
|
<View className='chat-header'>
|
||||||
|
<View className='chat-header__back' onClick={() => Taro.navigateBack()}>
|
||||||
|
<Text className='chat-header__back-text'>‹ 返回</Text>
|
||||||
|
</View>
|
||||||
|
<View className='chat-header__center'>
|
||||||
<Text className='chat-header__title'>{session?.subject || '在线咨询'}</Text>
|
<Text className='chat-header__title'>{session?.subject || '在线咨询'}</Text>
|
||||||
{!isOpen && (
|
<Text className={`chat-header__status ${isOpen ? '' : 'chat-header__status--closed'}`}>
|
||||||
<Text className='chat-header__status'>已结束</Text>
|
{statusLabel}
|
||||||
)}
|
</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
@@ -164,6 +171,11 @@ export default function ConsultationDetail() {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<View id={`msg-${idx + 1}`} className={`msg-row ${isSelf ? 'msg-row--self' : ''}`}>
|
<View id={`msg-${idx + 1}`} className={`msg-row ${isSelf ? 'msg-row--self' : ''}`}>
|
||||||
|
{!isSelf && (
|
||||||
|
<View className='msg-avatar'>
|
||||||
|
<Text className='msg-avatar-char'>{doctorInitial}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
<View className={`msg-bubble ${isSelf ? 'msg-bubble--self' : 'msg-bubble--other'}`}>
|
<View className={`msg-bubble ${isSelf ? 'msg-bubble--self' : 'msg-bubble--other'}`}>
|
||||||
{isImageUrl(msg.content) ? (
|
{isImageUrl(msg.content) ? (
|
||||||
<Image
|
<Image
|
||||||
@@ -203,7 +215,7 @@ export default function ConsultationDetail() {
|
|||||||
className={`chat-send-btn ${(!inputText.trim() || sending) ? 'chat-send-btn--disabled' : ''}`}
|
className={`chat-send-btn ${(!inputText.trim() || sending) ? 'chat-send-btn--disabled' : ''}`}
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
>
|
>
|
||||||
<Text className='chat-send-btn__text'>{sending ? '...' : '发送'}</Text>
|
<Text className='chat-send-btn__icon'>发</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -6,26 +6,36 @@
|
|||||||
background: $bg;
|
background: $bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 页头 ─── */
|
.consultation-body {
|
||||||
.consultation-header {
|
padding: 12px 24px 24px;
|
||||||
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
|
||||||
padding: 48px 32px 36px;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.consultation-title {
|
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
|
||||||
font-size: 40px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #fff;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── 副标题 ─── */
|
||||||
.consultation-subtitle {
|
.consultation-subtitle {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-cap);
|
||||||
color: rgba(255, 255, 255, 0.75);
|
color: var(--tk-text-secondary);
|
||||||
display: block;
|
display: block;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── 发起咨询按钮 — 实心主色 ─── */
|
||||||
|
.consultation-create-btn {
|
||||||
|
height: 48px;
|
||||||
|
border-radius: $r;
|
||||||
|
background: $pri;
|
||||||
|
@include flex-center;
|
||||||
|
box-shadow: 0 2px 8px rgba(196, 98, 58, 0.25);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.consultation-create-btn-text {
|
||||||
|
font-size: var(--tk-font-body-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 居中容器 ─── */
|
/* ─── 居中容器 ─── */
|
||||||
@@ -37,7 +47,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.consultation-error {
|
.consultation-error {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $dan;
|
color: $dan;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,51 +57,52 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 160px 40px;
|
padding: 120px 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-icon {
|
.empty-icon {
|
||||||
width: 120px;
|
width: 80px;
|
||||||
height: 120px;
|
height: 80px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: $pri-l;
|
background: $pri-l;
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-char {
|
.empty-char {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
@include serif-number;
|
||||||
font-size: 52px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: bold;
|
font-weight: 700;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
line-height: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-title {
|
.empty-title {
|
||||||
font-size: 32px;
|
font-size: var(--tk-font-body-sm);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-hint {
|
.empty-hint {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 会话列表 ─── */
|
/* ─── 会话列表 ─── */
|
||||||
.session-list {
|
.session-list {
|
||||||
padding: 20px 24px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.session-card {
|
.session-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
background: $card;
|
background: $card;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
padding: 24px;
|
padding: 16px;
|
||||||
margin-bottom: 12px;
|
|
||||||
box-shadow: $shadow-sm;
|
box-shadow: $shadow-sm;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -99,7 +110,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.session-main {
|
.session-card-closed {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-avatar {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 18px;
|
||||||
|
background: $pri-l;
|
||||||
|
@include flex-center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-avatar-char {
|
||||||
|
@include serif-number;
|
||||||
|
font-size: var(--tk-font-body-sm);
|
||||||
|
font-weight: 700;
|
||||||
|
color: $pri;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
@@ -108,56 +139,73 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.session-subject {
|
.session-subject {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: $tx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-right: 12px;
|
margin-right: 8px;
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
|
||||||
font-size: 26px;
|
|
||||||
color: $tx2;
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.session-time {
|
.session-time {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-micro);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
display: block;
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-tag {
|
||||||
|
font-size: var(--tk-font-micro);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
&.tag-ok { background: $acc-l; color: $acc; }
|
||||||
|
&.tag-warn { background: $wrn-l; color: $wrn; }
|
||||||
|
&.tag-default { background: $surface-alt; color: $tx3; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-message-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-message {
|
||||||
|
font-size: var(--tk-font-cap);
|
||||||
|
color: $tx2;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 未读角标 ─── */
|
/* ─── 未读角标 ─── */
|
||||||
.session-badge {
|
.session-badge {
|
||||||
background: $dan;
|
background: $dan;
|
||||||
border-radius: $r-pill;
|
border-radius: $r-pill;
|
||||||
min-width: 36px;
|
min-width: 18px;
|
||||||
height: 36px;
|
height: 18px;
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
padding: 0 10px;
|
padding: 0 5px;
|
||||||
margin-left: 12px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.session-badge-text {
|
.session-badge-text {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-micro);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useRef } from 'react';
|
||||||
import { View, Text } from '@tarojs/components';
|
import { View, Text } from '@tarojs/components';
|
||||||
import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro';
|
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||||
import { listConsultations, ConsultationSession } from '@/services/consultation';
|
import { listConsultations, ConsultationSession } from '@/services/consultation';
|
||||||
import Loading from '../../components/Loading';
|
import Loading from '../../components/Loading';
|
||||||
|
import { useElderClass } from '../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
function getStatusTag(status: string) {
|
function getStatusTag(status: string) {
|
||||||
@@ -33,42 +34,70 @@ export default function Consultation() {
|
|||||||
const [sessions, setSessions] = useState<ConsultationSession[]>([]);
|
const [sessions, setSessions] = useState<ConsultationSession[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
const modeClass = useElderClass();
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const loadingRef = useRef(false);
|
||||||
|
|
||||||
const loadSessions = async () => {
|
const loadSessions = async (pageNum: number, isRefresh = false) => {
|
||||||
setLoading(true);
|
if (loadingRef.current) return;
|
||||||
|
loadingRef.current = true;
|
||||||
|
if (isRefresh) setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
try {
|
try {
|
||||||
const resp = await listConsultations({ page: 1, page_size: 20 });
|
const resp = await listConsultations({ page: pageNum, page_size: 20 });
|
||||||
setSessions(resp.data || []);
|
const list = resp.data || [];
|
||||||
} catch (e: unknown) {
|
if (isRefresh) {
|
||||||
const msg = e instanceof Error ? e.message : '加载失败';
|
setSessions(list);
|
||||||
setError(msg);
|
} else {
|
||||||
|
setSessions((prev) => [...prev, ...list]);
|
||||||
|
}
|
||||||
|
setTotal(resp.total || 0);
|
||||||
|
setPage(pageNum);
|
||||||
|
} catch {
|
||||||
|
if (isRefresh) {
|
||||||
|
setSessions([]);
|
||||||
|
setTotal(0);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
loadingRef.current = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useDidShow(() => {
|
useDidShow(() => {
|
||||||
Taro.setNavigationBarTitle({ title: '在线咨询' });
|
Taro.setNavigationBarTitle({ title: '在线咨询' });
|
||||||
loadSessions();
|
loadSessions(1, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
usePullDownRefresh(() => {
|
usePullDownRefresh(() => {
|
||||||
loadSessions().finally(() => {
|
loadSessions(1, true).finally(() => {
|
||||||
Taro.stopPullDownRefresh();
|
Taro.stopPullDownRefresh();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useReachBottom(() => {
|
||||||
|
if (!loading && sessions.length < total) {
|
||||||
|
loadSessions(page + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const handleTapSession = (session: ConsultationSession) => {
|
const handleTapSession = (session: ConsultationSession) => {
|
||||||
Taro.navigateTo({ url: `/pages/consultation/detail/index?id=${session.id}` });
|
Taro.navigateTo({ url: `/pages/consultation/detail/index?id=${session.id}` });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='consultation-page'>
|
<View className={`consultation-page ${modeClass}`}>
|
||||||
{/* 页头 */}
|
<View className='consultation-body'>
|
||||||
<View className='consultation-header'>
|
{/* 副标题 */}
|
||||||
<Text className='consultation-title'>在线咨询</Text>
|
|
||||||
<Text className='consultation-subtitle'>随时随地,连接专业医生</Text>
|
<Text className='consultation-subtitle'>随时随地,连接专业医生</Text>
|
||||||
|
|
||||||
|
{/* 发起咨询按钮 — 实心主色 */}
|
||||||
|
<View
|
||||||
|
className='consultation-create-btn'
|
||||||
|
onClick={() => Taro.navigateTo({ url: '/pages/consultation/create/index' })}
|
||||||
|
>
|
||||||
|
<Text className='consultation-create-btn-text'>发起咨询</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 内容区 */}
|
{/* 内容区 */}
|
||||||
@@ -92,28 +121,35 @@ export default function Consultation() {
|
|||||||
<View className='session-list'>
|
<View className='session-list'>
|
||||||
{sessions.map((session) => {
|
{sessions.map((session) => {
|
||||||
const tag = getStatusTag(session.status);
|
const tag = getStatusTag(session.status);
|
||||||
|
const initial = (session.subject || '咨').charAt(0);
|
||||||
|
const isClosed = session.status === 'closed' || session.status === 'cancelled';
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
key={session.id}
|
key={session.id}
|
||||||
className='session-card'
|
className={`session-card ${isClosed ? 'session-card-closed' : ''}`}
|
||||||
onClick={() => handleTapSession(session)}
|
onClick={() => handleTapSession(session)}
|
||||||
>
|
>
|
||||||
<View className='session-main'>
|
<View className='session-avatar'>
|
||||||
|
<Text className='session-avatar-char'>{initial}</Text>
|
||||||
|
</View>
|
||||||
|
<View className='session-body'>
|
||||||
<View className='session-top'>
|
<View className='session-top'>
|
||||||
<Text className='session-subject'>
|
<Text className='session-subject'>
|
||||||
{session.subject || '在线咨询'}
|
{session.subject || '在线咨询'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className={`session-tag ${tag.cls}`}>{tag.label}</Text>
|
|
||||||
</View>
|
|
||||||
<Text className='session-message'>
|
|
||||||
{session.last_message || '暂无消息'}
|
|
||||||
</Text>
|
|
||||||
<Text className='session-time'>
|
<Text className='session-time'>
|
||||||
{session.last_message_at
|
{session.last_message_at
|
||||||
? formatTime(session.last_message_at)
|
? formatTime(session.last_message_at)
|
||||||
: formatTime(session.created_at)}
|
: formatTime(session.created_at)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
<View className='session-meta'>
|
||||||
|
<Text className={`session-tag ${tag.cls}`}>{tag.label}</Text>
|
||||||
|
</View>
|
||||||
|
<View className='session-message-row'>
|
||||||
|
<Text className='session-message'>
|
||||||
|
{session.last_message || '暂无消息'}
|
||||||
|
</Text>
|
||||||
{session.unread_count_patient > 0 && (
|
{session.unread_count_patient > 0 && (
|
||||||
<View className='session-badge'>
|
<View className='session-badge'>
|
||||||
<Text className='session-badge-text'>
|
<Text className='session-badge-text'>
|
||||||
@@ -122,10 +158,13 @@ export default function Consultation() {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,5 @@
|
|||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
@import '../../styles/mixins.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 {
|
.device-sync-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -43,9 +14,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sync-header-title {
|
.sync-header-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
@include section-title;
|
||||||
font-size: 40px;
|
color: $card;
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-section {
|
.sync-section {
|
||||||
@@ -72,20 +42,17 @@
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 36px;
|
font-size: var(--tk-font-num-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-hero-title {
|
.sync-hero-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
@include section-title;
|
||||||
font-size: 34px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: $tx;
|
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-hero-desc {
|
.sync-hero-desc {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +77,7 @@
|
|||||||
|
|
||||||
.sync-action-text {
|
.sync-action-text {
|
||||||
color: $card;
|
color: $card;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +87,7 @@
|
|||||||
|
|
||||||
.sync-section-title {
|
.sync-section-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
@@ -144,19 +111,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sync-device-name {
|
.sync-device-name {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-device-adapter {
|
.sync-device-adapter {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-device-rssi {
|
.sync-device-rssi {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +150,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sync-status-text {
|
.sync-status-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,12 +175,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sync-reading-type {
|
.sync-reading-type {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-reading-value {
|
.sync-reading-value {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
@@ -222,8 +189,8 @@
|
|||||||
.sync-readings-count {
|
.sync-readings-count {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +207,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sync-error-text {
|
.sync-error-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $dan;
|
color: $dan;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +217,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sync-loading-text {
|
.sync-loading-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,20 +240,17 @@
|
|||||||
@include flex-center;
|
@include flex-center;
|
||||||
color: $acc;
|
color: $acc;
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 36px;
|
font-size: var(--tk-font-num-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-result-title {
|
.sync-result-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
@include section-title;
|
||||||
font-size: 34px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: $tx;
|
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-result-count {
|
.sync-result-count {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { DataSyncScheduler } from '@/services/ble/DataSyncScheduler';
|
|||||||
import { uploadReadings } from '@/services/device-sync';
|
import { uploadReadings } from '@/services/device-sync';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import type { BLEDevice, NormalizedReading } from '@/services/ble/types';
|
import type { BLEDevice, NormalizedReading } from '@/services/ble/types';
|
||||||
|
import { useElderClass } from '../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const bleManager = new BLEManager({ scanTimeout: 10000, retryCount: 3 });
|
const bleManager = new BLEManager({ scanTimeout: 10000, retryCount: 3 });
|
||||||
@@ -21,6 +22,7 @@ bleManager.registerAdapter(CustomBandAdapter);
|
|||||||
type PageState = 'idle' | 'scanning' | 'connecting' | 'connected' | 'syncing' | 'done' | 'error';
|
type PageState = 'idle' | 'scanning' | 'connecting' | 'connected' | 'syncing' | 'done' | 'error';
|
||||||
|
|
||||||
export default function DeviceSync() {
|
export default function DeviceSync() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const { currentPatient } = useAuthStore();
|
const { currentPatient } = useAuthStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const returnTo = router.params.returnTo || '';
|
const returnTo = router.params.returnTo || '';
|
||||||
@@ -271,7 +273,7 @@ export default function DeviceSync() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="device-sync-page">
|
<View className={`device-sync-page ${modeClass}`}>
|
||||||
<View className="sync-header">
|
<View className="sync-header">
|
||||||
<Text className="sync-header-title">设备同步</Text>
|
<Text className="sync-header-title">设备同步</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
|
@import '../../../styles/variables.scss';
|
||||||
|
@import '../../../styles/mixins.scss';
|
||||||
|
|
||||||
.action-inbox-page {
|
.action-inbox-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #f5f5f5;
|
background: $bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inbox-tabs {
|
.inbox-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
background: white;
|
background: $card;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid $bd;
|
||||||
|
|
||||||
.inbox-tab {
|
.inbox-tab {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -16,7 +19,7 @@
|
|||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
.inbox-tab-text {
|
.inbox-tab-text {
|
||||||
color: #C4623A;
|
color: $pri;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@@ -27,7 +30,7 @@
|
|||||||
left: 30%;
|
left: 30%;
|
||||||
right: 30%;
|
right: 30%;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
background: #C4623A;
|
background: $pri;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,8 +38,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.inbox-tab-text {
|
.inbox-tab-text {
|
||||||
font-size: 14px;
|
font-size: var(--tk-font-cap);
|
||||||
color: #666;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,10 +49,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.inbox-card {
|
.inbox-card {
|
||||||
background: white;
|
background: $card;
|
||||||
border-radius: 12px;
|
border-radius: $r-sm;
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
box-shadow: $shadow-sm;
|
||||||
|
|
||||||
.inbox-card-header {
|
.inbox-card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -59,21 +63,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.inbox-type-tag {
|
.inbox-type-tag {
|
||||||
color: white;
|
color: $card;
|
||||||
font-size: 10px;
|
font-size: var(--tk-font-micro);
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inbox-card-title {
|
.inbox-card-title {
|
||||||
font-size: 14px;
|
font-size: var(--tk-font-cap);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inbox-card-desc {
|
.inbox-card-desc {
|
||||||
font-size: 12px;
|
font-size: var(--tk-font-micro);
|
||||||
color: #999;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,38 +87,39 @@
|
|||||||
padding: 80px 0;
|
padding: 80px 0;
|
||||||
|
|
||||||
.inbox-empty-text {
|
.inbox-empty-text {
|
||||||
font-size: 14px;
|
font-size: var(--tk-font-cap);
|
||||||
color: #999;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 半屏弹窗
|
|
||||||
.half-screen-dialog {
|
.half-screen-dialog {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
max-height: 70vh;
|
max-height: 70vh;
|
||||||
background: white;
|
background: $card;
|
||||||
border-radius: 16px 16px 0 0;
|
border-radius: $r-lg $r-lg 0 0;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
box-shadow: $shadow-lg;
|
||||||
|
|
||||||
.dialog-header {
|
.dialog-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid $bd-l;
|
||||||
|
|
||||||
.dialog-title {
|
.dialog-title {
|
||||||
font-size: 16px;
|
font-size: var(--tk-font-body-sm);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-close {
|
.dialog-close {
|
||||||
font-size: 13px;
|
font-size: var(--tk-font-cap);
|
||||||
color: #999;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,8 +128,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dialog-patient {
|
.dialog-patient {
|
||||||
font-size: 13px;
|
font-size: var(--tk-font-cap);
|
||||||
color: #666;
|
color: $tx2;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
@@ -142,21 +148,22 @@
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
&.completed { background: #52c41a; }
|
&.completed { background: $acc; }
|
||||||
&.in_progress { background: #faad14; }
|
&.in_progress { background: $wrn; }
|
||||||
&.pending { background: #d9d9d9; }
|
&.pending { background: $tx3; }
|
||||||
&.dismissed { background: #ff4d4f; }
|
&.dismissed { background: $dan; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-content {
|
.thread-content {
|
||||||
.thread-label {
|
.thread-label {
|
||||||
font-size: 13px;
|
font-size: var(--tk-font-cap);
|
||||||
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-time {
|
.thread-time {
|
||||||
font-size: 11px;
|
font-size: var(--tk-font-micro);
|
||||||
color: #999;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,19 +171,19 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 12px 20px 20px;
|
padding: 12px 20px 20px;
|
||||||
border-top: 1px solid #f0f0f0;
|
border-top: 1px solid $bd-l;
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 8px;
|
border-radius: $r-sm;
|
||||||
font-size: 14px;
|
font-size: var(--tk-font-cap);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
&.primary { background: #C4623A; color: white; }
|
&.primary { background: $pri; color: $card; }
|
||||||
&.danger { background: #ff4d4f; color: white; }
|
&.danger { background: $dan; color: $card; }
|
||||||
&.default { background: #f5f5f5; color: #666; }
|
&.default { background: $surface-alt; color: $tx2; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
type ThreadResponse,
|
type ThreadResponse,
|
||||||
} from '@/services/action-inbox';
|
} from '@/services/action-inbox';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
|
import { getStatusInlineStyle, getStatusLabel } from '@/utils/statusTag';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const TYPE_LABEL: Record<string, string> = {
|
const TYPE_LABEL: Record<string, string> = {
|
||||||
@@ -32,6 +34,7 @@ const STATUS_TABS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function ActionInboxPage() {
|
export default function ActionInboxPage() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [items, setItems] = useState<ActionItem[]>([]);
|
const [items, setItems] = useState<ActionItem[]>([]);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [_page, setPage] = useState(1);
|
const [_page, setPage] = useState(1);
|
||||||
@@ -117,7 +120,7 @@ export default function ActionInboxPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="action-inbox-page">
|
<View className={`action-inbox-page ${modeClass}`}>
|
||||||
<View className="inbox-tabs">
|
<View className="inbox-tabs">
|
||||||
{STATUS_TABS.map((tab) => (
|
{STATUS_TABS.map((tab) => (
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -18,13 +18,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__time {
|
&__time {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-severity {
|
.detail-severity {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 6px 16px;
|
padding: 6px 16px;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-status {
|
.detail-status {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
padding: 6px 16px;
|
padding: 6px 16px;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
|
|
||||||
@@ -84,24 +84,24 @@
|
|||||||
box-shadow: $shadow-sm;
|
box-shadow: $shadow-sm;
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__value {
|
&__value {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
|
||||||
&--id {
|
&--id {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--detail {
|
&--detail {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
height: 88px;
|
height: 88px;
|
||||||
line-height: 88px;
|
line-height: 88px;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-radius: $r-lg;
|
border-radius: $r-lg;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { View, Text, ScrollView, Button } from '@tarojs/components';
|
|||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '../../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const SEVERITY_MAP: Record<string, { label: string; className: string }> = {
|
const SEVERITY_MAP: Record<string, { label: string; className: string }> = {
|
||||||
@@ -20,6 +21,7 @@ const STATUS_MAP: Record<string, { label: string; className: string }> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function AlertDetail() {
|
export default function AlertDetail() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [alert, setAlert] = useState<doctorApi.Alert | null>(null);
|
const [alert, setAlert] = useState<doctorApi.Alert | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [actionLoading, setActionLoading] = useState(false);
|
const [actionLoading, setActionLoading] = useState(false);
|
||||||
@@ -93,7 +95,7 @@ export default function AlertDetail() {
|
|||||||
if (loading) return <Loading />;
|
if (loading) return <Loading />;
|
||||||
if (!alert) {
|
if (!alert) {
|
||||||
return (
|
return (
|
||||||
<View className='alert-detail-page'>
|
<View className={`alert-detail-page ${modeClass}`}>
|
||||||
<Text>告警不存在</Text>
|
<Text>告警不存在</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -105,7 +107,7 @@ export default function AlertDetail() {
|
|||||||
const isAcknowledged = alert.status === 'acknowledged';
|
const isAcknowledged = alert.status === 'acknowledged';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='alert-detail-page'>
|
<ScrollView scrollY className={`alert-detail-page ${modeClass}`}>
|
||||||
{/* 顶部状态 */}
|
{/* 顶部状态 */}
|
||||||
<View className='alert-detail-header'>
|
<View className='alert-detail-header'>
|
||||||
<View className='alert-detail-header__tags'>
|
<View className='alert-detail-header__tags'>
|
||||||
|
|||||||
@@ -16,13 +16,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.alert-list-title {
|
.alert-list-title {
|
||||||
font-size: 36px;
|
font-size: var(--tk-font-num-lg);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-list-count {
|
.alert-list-count {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
padding: 10px 24px;
|
padding: 10px 24px;
|
||||||
border-radius: $r-pill;
|
border-radius: $r-pill;
|
||||||
background: $bd-l;
|
background: $bd-l;
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -92,13 +92,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__time {
|
&__time {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-severity {
|
.alert-severity {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.alert-status {
|
.alert-status {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
|
|
||||||
&__btn {
|
&__btn {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { View, Text, ScrollView } from '@tarojs/components';
|
import { View, Text, ScrollView } from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import EmptyState from '@/components/EmptyState';
|
import EmptyState from '@/components/EmptyState';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const SEVERITY_MAP: Record<string, { label: string; className: string }> = {
|
const SEVERITY_MAP: Record<string, { label: string; className: string }> = {
|
||||||
@@ -28,12 +29,15 @@ const STATUS_TABS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function AlertList() {
|
export default function AlertList() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [alerts, setAlerts] = useState<doctorApi.Alert[]>([]);
|
const [alerts, setAlerts] = useState<doctorApi.Alert[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [activeTab, setActiveTab] = useState('');
|
const [activeTab, setActiveTab] = useState('');
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
|
const totalPages = useMemo(() => Math.ceil(total / 20), [total]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadAlerts();
|
loadAlerts();
|
||||||
}, [page, activeTab]);
|
}, [page, activeTab]);
|
||||||
@@ -79,7 +83,7 @@ export default function AlertList() {
|
|||||||
if (loading && alerts.length === 0) return <Loading />;
|
if (loading && alerts.length === 0) return <Loading />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='alert-list-page'>
|
<ScrollView scrollY className={`alert-list-page ${modeClass}`}>
|
||||||
<View className='alert-list-header'>
|
<View className='alert-list-header'>
|
||||||
<Text className='alert-list-title'>告警列表</Text>
|
<Text className='alert-list-title'>告警列表</Text>
|
||||||
<Text className='alert-list-count'>共 {total} 条</Text>
|
<Text className='alert-list-count'>共 {total} 条</Text>
|
||||||
@@ -137,11 +141,11 @@ export default function AlertList() {
|
|||||||
上一页
|
上一页
|
||||||
</Text>
|
</Text>
|
||||||
<Text className='alert-pagination__info'>
|
<Text className='alert-pagination__info'>
|
||||||
{page} / {Math.ceil(total / 20)}
|
{page} / {totalPages}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
className={`alert-pagination__btn ${page >= Math.ceil(total / 20) ? 'disabled' : ''}`}
|
className={`alert-pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
|
||||||
onClick={() => page < Math.ceil(total / 20) && setPage(page + 1)}
|
onClick={() => page < totalPages && setPage(page + 1)}
|
||||||
>
|
>
|
||||||
下一页
|
下一页
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -18,13 +18,13 @@
|
|||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__close {
|
&__close {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $dan;
|
color: $dan;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.msg-text {
|
.msg-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
|
|
||||||
.msg-time {
|
.msg-time {
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
padding: 120px 32px;
|
padding: 120px 32px;
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
background: $bd-l;
|
background: $bd-l;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $card;
|
color: $card;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
border-top: 1px solid $bd;
|
border-top: 1px solid $bd;
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,56 +3,47 @@ import { View, Text, Input, ScrollView } from '@tarojs/components';
|
|||||||
import Taro, { useRouter } from '@tarojs/taro';
|
import Taro, { useRouter } from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '../../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const POLL_INTERVAL = 8000;
|
|
||||||
|
|
||||||
export default function ConsultationDetail() {
|
export default function ConsultationDetail() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const sessionId = router.params.id || '';
|
const sessionId = router.params.id || '';
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [session, setSession] = useState<doctorApi.ConsultationSession | null>(null);
|
const [session, setSession] = useState<doctorApi.ConsultationSession | null>(null);
|
||||||
const [messages, setMessages] = useState<doctorApi.ConsultationMessage[]>([]);
|
const [messages, setMessages] = useState<doctorApi.ConsultationMessage[]>([]);
|
||||||
const [inputText, setInputText] = useState('');
|
const [inputText, setInputText] = useState('');
|
||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const scrollViewRef = useRef('');
|
const scrollViewRef = useRef('');
|
||||||
const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
const pollingRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
loadData();
|
loadData();
|
||||||
markRead();
|
markRead();
|
||||||
startPolling();
|
startLongPolling();
|
||||||
}
|
}
|
||||||
return () => stopPolling();
|
return () => { pollingRef.current = false; };
|
||||||
}, [sessionId]);
|
}, [sessionId]);
|
||||||
|
|
||||||
const startPolling = () => {
|
useEffect(() => {
|
||||||
stopPolling();
|
if (session?.status === 'closed') {
|
||||||
pollTimerRef.current = setInterval(pollNewMessages, POLL_INTERVAL);
|
pollingRef.current = false;
|
||||||
|
}
|
||||||
|
}, [session?.status]);
|
||||||
|
|
||||||
|
const startLongPolling = () => {
|
||||||
|
pollingRef.current = true;
|
||||||
|
longPoll();
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopPolling = () => {
|
const longPoll = async () => {
|
||||||
if (pollTimerRef.current) {
|
if (!pollingRef.current) return;
|
||||||
clearInterval(pollTimerRef.current);
|
|
||||||
pollTimerRef.current = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const pollNewMessages = async () => {
|
|
||||||
if (!session || session.status === 'closed') {
|
|
||||||
stopPolling();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const lastId = messages.length > 0 ? messages[messages.length - 1].id : undefined;
|
const lastId = messages.length > 0 ? messages[messages.length - 1].id : undefined;
|
||||||
const m = await doctorApi.listMessages(sessionId, {
|
const newMsgs = await doctorApi.pollMessages(sessionId, lastId);
|
||||||
page: 1,
|
if (newMsgs && newMsgs.length > 0) {
|
||||||
page_size: 50,
|
|
||||||
after_id: lastId,
|
|
||||||
});
|
|
||||||
const newMsgs = m.data || [];
|
|
||||||
if (newMsgs.length > 0) {
|
|
||||||
setMessages((prev) => {
|
setMessages((prev) => {
|
||||||
const existing = new Set(prev.map((msg) => msg.id));
|
const existing = new Set(prev.map((msg) => msg.id));
|
||||||
const fresh = newMsgs.filter((msg) => !existing.has(msg.id));
|
const fresh = newMsgs.filter((msg) => !existing.has(msg.id));
|
||||||
@@ -60,7 +51,12 @@ export default function ConsultationDetail() {
|
|||||||
});
|
});
|
||||||
scrollViewRef.current = `msg-${messages.length + newMsgs.length}`;
|
scrollViewRef.current = `msg-${messages.length + newMsgs.length}`;
|
||||||
}
|
}
|
||||||
} catch { /* 轮询失败静默忽略 */ }
|
} catch {
|
||||||
|
// 超时或网络错误,静默重试
|
||||||
|
}
|
||||||
|
if (pollingRef.current) {
|
||||||
|
longPoll();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@@ -73,7 +69,7 @@ export default function ConsultationDetail() {
|
|||||||
setSession(s);
|
setSession(s);
|
||||||
setMessages(m.data || []);
|
setMessages(m.data || []);
|
||||||
scrollViewRef.current = `msg-${(m.data || []).length}`;
|
scrollViewRef.current = `msg-${(m.data || []).length}`;
|
||||||
if (s.status === 'closed') stopPolling();
|
if (s.status === 'closed') pollingRef.current = false;
|
||||||
} catch {
|
} catch {
|
||||||
Taro.showToast({ title: '加载失败', icon: 'none' });
|
Taro.showToast({ title: '加载失败', icon: 'none' });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -132,7 +128,7 @@ export default function ConsultationDetail() {
|
|||||||
const isOpen = session?.status !== 'closed';
|
const isOpen = session?.status !== 'closed';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='chat-page'>
|
<View className={`chat-page ${modeClass}`}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<View className='chat-header'>
|
<View className='chat-header'>
|
||||||
<Text className='chat-header__title'>{session?.subject || '在线咨询'}</Text>
|
<Text className='chat-header__title'>{session?.subject || '在线咨询'}</Text>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 24px 0;
|
padding: 24px 0;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__subject {
|
&__subject {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__status-text {
|
&__status-text {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,12 +96,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__time {
|
&__time {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__preview {
|
&__preview {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
|
|
||||||
&__badge-text {
|
&__badge-text {
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $card;
|
color: $card;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
padding: 24px;
|
padding: 24px;
|
||||||
|
|
||||||
&__btn {
|
&__btn {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { View, Text, ScrollView } from '@tarojs/components';
|
import { View, Text, ScrollView } from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import EmptyState from '@/components/EmptyState';
|
import EmptyState from '@/components/EmptyState';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
|
import { getStatusInlineStyle, getStatusLabel } from '@/utils/statusTag';
|
||||||
|
import { formatDateTime } from '@/utils/date';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const STATUS_MAP: Record<string, { label: string; color: string }> = {
|
|
||||||
waiting: { label: '等待中', color: '#f59e0b' },
|
|
||||||
active: { label: '进行中', color: '#10b981' },
|
|
||||||
closed: { label: '已关闭', color: '#94a3b8' },
|
|
||||||
};
|
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{ key: '', label: '全部' },
|
{ key: '', label: '全部' },
|
||||||
{ key: 'active', label: '进行中' },
|
{ key: 'active', label: '进行中' },
|
||||||
@@ -20,12 +17,15 @@ const TABS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function ConsultationList() {
|
export default function ConsultationList() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [sessions, setSessions] = useState<doctorApi.ConsultationSession[]>([]);
|
const [sessions, setSessions] = useState<doctorApi.ConsultationSession[]>([]);
|
||||||
const [activeTab, setActiveTab] = useState('');
|
const [activeTab, setActiveTab] = useState('');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
|
const totalPages = useMemo(() => Math.ceil(total / 20), [total]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadSessions();
|
loadSessions();
|
||||||
}, [page, activeTab]);
|
}, [page, activeTab]);
|
||||||
@@ -54,18 +54,13 @@ export default function ConsultationList() {
|
|||||||
|
|
||||||
const formatTime = (dateStr?: string | null) => {
|
const formatTime = (dateStr?: string | null) => {
|
||||||
if (!dateStr) return '';
|
if (!dateStr) return '';
|
||||||
const d = new Date(dateStr);
|
return formatDateTime(dateStr);
|
||||||
const now = new Date();
|
|
||||||
if (d.toDateString() === now.toDateString()) {
|
|
||||||
return d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
|
|
||||||
}
|
|
||||||
return d.toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading && sessions.length === 0) return <Loading />;
|
if (loading && sessions.length === 0) return <Loading />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='consultation-page'>
|
<ScrollView scrollY className={`consultation-page ${modeClass}`}>
|
||||||
<View className='tabs'>
|
<View className='tabs'>
|
||||||
{TABS.map((t) => (
|
{TABS.map((t) => (
|
||||||
<View
|
<View
|
||||||
@@ -83,7 +78,6 @@ export default function ConsultationList() {
|
|||||||
) : (
|
) : (
|
||||||
<View className='session-list'>
|
<View className='session-list'>
|
||||||
{sessions.map((s) => {
|
{sessions.map((s) => {
|
||||||
const st = STATUS_MAP[s.status] || { label: s.status, color: '#94a3b8' };
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
key={s.id}
|
key={s.id}
|
||||||
@@ -92,8 +86,8 @@ export default function ConsultationList() {
|
|||||||
>
|
>
|
||||||
<View className='session-card__top'>
|
<View className='session-card__top'>
|
||||||
<Text className='session-card__subject'>{s.subject || '在线咨询'}</Text>
|
<Text className='session-card__subject'>{s.subject || '在线咨询'}</Text>
|
||||||
<View className='session-card__status' style={`background: ${st.color}20; color: ${st.color}`}>
|
<View className='session-card__status' style={getStatusInlineStyle(s.status)}>
|
||||||
<Text className='session-card__status-text'>{st.label}</Text>
|
<Text className='session-card__status-text'>{getStatusLabel(s.status)}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View className='session-card__info'>
|
<View className='session-card__info'>
|
||||||
@@ -122,10 +116,10 @@ export default function ConsultationList() {
|
|||||||
className={`pagination__btn ${page <= 1 ? 'disabled' : ''}`}
|
className={`pagination__btn ${page <= 1 ? 'disabled' : ''}`}
|
||||||
onClick={() => page > 1 && setPage(page - 1)}
|
onClick={() => page > 1 && setPage(page - 1)}
|
||||||
>上一页</Text>
|
>上一页</Text>
|
||||||
<Text className='pagination__info'>{page} / {Math.ceil(total / 20)}</Text>
|
<Text className='pagination__info'>{page} / {totalPages}</Text>
|
||||||
<Text
|
<Text
|
||||||
className={`pagination__btn ${page >= Math.ceil(total / 20) ? 'disabled' : ''}`}
|
className={`pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
|
||||||
onClick={() => page < Math.ceil(total / 20) && setPage(page + 1)}
|
onClick={() => page < totalPages && setPage(page + 1)}
|
||||||
>下一页</Text>
|
>下一页</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import '../../../../styles/variables.scss';
|
@import '../../../../styles/variables.scss';
|
||||||
|
@import '../../../../styles/mixins.scss';
|
||||||
|
|
||||||
.create-page {
|
.create-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-label {
|
.form-label {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
@@ -50,12 +51,12 @@
|
|||||||
.form-input {
|
.form-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-value {
|
.form-value {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
|
|
||||||
&.placeholder {
|
&.placeholder {
|
||||||
@@ -66,7 +67,7 @@
|
|||||||
.form-textarea {
|
.form-textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
min-height: 120px;
|
min-height: 120px;
|
||||||
background: $bg;
|
background: $bg;
|
||||||
@@ -91,7 +92,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.submit-btn__text {
|
.submit-btn__text {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { View, Text, Input, Textarea, Picker, ScrollView } from '@tarojs/compone
|
|||||||
import Taro, { useRouter } from '@tarojs/taro';
|
import Taro, { useRouter } from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '../../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const DIALYSIS_TYPES = ['HD', 'HDF', 'HF'];
|
const DIALYSIS_TYPES = ['HD', 'HDF', 'HF'];
|
||||||
@@ -55,6 +56,7 @@ export default function DialysisCreate() {
|
|||||||
const version = router.params.version ? Number(router.params.version) : 0;
|
const version = router.params.version ? Number(router.params.version) : 0;
|
||||||
const patientIdFromRoute = router.params.patientId || '';
|
const patientIdFromRoute = router.params.patientId || '';
|
||||||
const isEdit = !!id;
|
const isEdit = !!id;
|
||||||
|
const modeClass = useElderClass();
|
||||||
|
|
||||||
const [form, setForm] = useState<FormState>({ ...initialForm, patient_id: patientIdFromRoute });
|
const [form, setForm] = useState<FormState>({ ...initialForm, patient_id: patientIdFromRoute });
|
||||||
const [loading, setLoading] = useState(isEdit);
|
const [loading, setLoading] = useState(isEdit);
|
||||||
@@ -167,7 +169,7 @@ export default function DialysisCreate() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='create-page'>
|
<ScrollView scrollY className={`create-page ${modeClass}`}>
|
||||||
<View className='section'>
|
<View className='section'>
|
||||||
<Text className='section-title'>基本信息</Text>
|
<Text className='section-title'>基本信息</Text>
|
||||||
<View className='form-row'>
|
<View className='form-row'>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import '../../../../styles/variables.scss';
|
@import '../../../../styles/variables.scss';
|
||||||
|
@import '../../../../styles/mixins.scss';
|
||||||
|
|
||||||
.dialysis-detail {
|
.dialysis-detail {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -31,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.record-header__title {
|
.record-header__title {
|
||||||
font-size: 34px;
|
font-size: var(--tk-font-num-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
background: $bd-l;
|
background: $bd-l;
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
|
|
||||||
@@ -57,13 +58,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.record-sub {
|
.record-sub {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-info {
|
.review-info {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
@@ -82,12 +83,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-label {
|
.detail-label {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-value {
|
.detail-value {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -98,7 +99,7 @@
|
|||||||
.error-text {
|
.error-text {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 120px 0;
|
padding: 120px 0;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +151,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-btn__text {
|
.action-btn__text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { View, Text, ScrollView } from '@tarojs/components';
|
|||||||
import Taro, { useRouter } from '@tarojs/taro';
|
import Taro, { useRouter } from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '../../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
export default function DialysisDetail() {
|
export default function DialysisDetail() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const id = router.params.id || '';
|
const id = router.params.id || '';
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [record, setRecord] = useState<doctorApi.DialysisRecord | null>(null);
|
const [record, setRecord] = useState<doctorApi.DialysisRecord | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
@@ -85,13 +87,13 @@ export default function DialysisDetail() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (loading) return <Loading />;
|
if (loading) return <Loading />;
|
||||||
if (!record) return <View className='error-text'><Text>记录加载失败</Text></View>;
|
if (!record) return <View className={`error-text ${modeClass}`}><Text>记录加载失败</Text></View>;
|
||||||
|
|
||||||
const canComplete = record.status === 'draft';
|
const canComplete = record.status === 'draft';
|
||||||
const canReview = record.status === 'completed';
|
const canReview = record.status === 'completed';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='dialysis-detail'>
|
<ScrollView scrollY className={`dialysis-detail ${modeClass}`}>
|
||||||
{/* 状态头部 */}
|
{/* 状态头部 */}
|
||||||
<View className='section'>
|
<View className='section'>
|
||||||
<View className='record-header'>
|
<View className='record-header'>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import '../../../styles/variables.scss';
|
@import '../../../styles/variables.scss';
|
||||||
|
@import '../../../styles/mixins.scss';
|
||||||
|
|
||||||
.dialysis-page {
|
.dialysis-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -15,7 +16,7 @@
|
|||||||
background: $bg;
|
background: $bg;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab-text {
|
.tab-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.record-count {
|
.record-count {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
padding: 8px 0 16px;
|
padding: 8px 0 16px;
|
||||||
}
|
}
|
||||||
@@ -89,7 +90,7 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background: $pri-l;
|
background: $pri-l;
|
||||||
color: $pri-d;
|
color: $pri-d;
|
||||||
@@ -109,7 +110,7 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-body);
|
||||||
background: $bd-l;
|
background: $bd-l;
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
|
|
||||||
@@ -131,13 +132,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.record-card__date {
|
.record-card__date {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
.record-card__meta {
|
.record-card__meta {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
@@ -154,7 +155,7 @@
|
|||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
background: $card;
|
background: $card;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
|
|
||||||
&--disabled {
|
&--disabled {
|
||||||
@@ -163,7 +164,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page-info {
|
.page-info {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +188,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fab-text {
|
.fab-text {
|
||||||
font-size: 40px;
|
font-size: var(--tk-font-hero);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Taro, { useRouter } from '@tarojs/taro';
|
|||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import EmptyState from '@/components/EmptyState';
|
import EmptyState from '@/components/EmptyState';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
@@ -18,6 +19,7 @@ const TYPE_MAP: Record<string, string> = { HD: 'HD', HDF: 'HDF', HF: 'HF' };
|
|||||||
export default function DialysisList() {
|
export default function DialysisList() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const patientId = router.params.patientId || '';
|
const patientId = router.params.patientId || '';
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [searchPatient, setSearchPatient] = useState('');
|
const [searchPatient, setSearchPatient] = useState('');
|
||||||
const [currentPatientId, setCurrentPatientId] = useState(patientId);
|
const [currentPatientId, setCurrentPatientId] = useState(patientId);
|
||||||
const [activeTab, setActiveTab] = useState('');
|
const [activeTab, setActiveTab] = useState('');
|
||||||
@@ -72,7 +74,7 @@ export default function DialysisList() {
|
|||||||
if (loading && records.length === 0) return <Loading />;
|
if (loading && records.length === 0) return <Loading />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='dialysis-page'>
|
<ScrollView scrollY className={`dialysis-page ${modeClass}`}>
|
||||||
{!patientId && (
|
{!patientId && (
|
||||||
<View className='search-bar'>
|
<View className='search-bar'>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -28,13 +28,13 @@
|
|||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__status {
|
&__status {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
padding: 6px 16px;
|
padding: 6px 16px;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -60,12 +60,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-label {
|
.info-label {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-value {
|
.info-value {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -77,14 +77,14 @@
|
|||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
@@ -99,14 +99,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__date {
|
&__date {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
color: $card;
|
color: $card;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-label {
|
.form-label {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
background: $bd-l;
|
background: $bd-l;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
background: $bd-l;
|
background: $bd-l;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $card;
|
color: $card;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -181,5 +181,5 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 80px 32px;
|
padding: 80px 32px;
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { View, Text, Textarea, ScrollView } from '@tarojs/components';
|
|||||||
import Taro, { useRouter } from '@tarojs/taro';
|
import Taro, { useRouter } from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '../../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const STATUS_LABELS: Record<string, string> = {
|
const STATUS_LABELS: Record<string, string> = {
|
||||||
@@ -16,6 +17,7 @@ const STATUS_LABELS: Record<string, string> = {
|
|||||||
export default function FollowUpDetail() {
|
export default function FollowUpDetail() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const taskId = router.params.id || '';
|
const taskId = router.params.id || '';
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [task, setTask] = useState<doctorApi.FollowUpTask | null>(null);
|
const [task, setTask] = useState<doctorApi.FollowUpTask | null>(null);
|
||||||
const [records, setRecords] = useState<doctorApi.FollowUpRecord[]>([]);
|
const [records, setRecords] = useState<doctorApi.FollowUpRecord[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -87,12 +89,12 @@ export default function FollowUpDetail() {
|
|||||||
const formatDate = (d: string) => new Date(d).toLocaleDateString('zh-CN');
|
const formatDate = (d: string) => new Date(d).toLocaleDateString('zh-CN');
|
||||||
|
|
||||||
if (loading) return <Loading />;
|
if (loading) return <Loading />;
|
||||||
if (!task) return <View className='error-text'><Text>任务加载失败</Text></View>;
|
if (!task) return <View className={`error-text ${modeClass}`}><Text>任务加载失败</Text></View>;
|
||||||
|
|
||||||
const canSubmit = task.status === 'in_progress' || task.status === 'pending' || task.status === 'overdue';
|
const canSubmit = task.status === 'in_progress' || task.status === 'pending' || task.status === 'overdue';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='followup-detail'>
|
<ScrollView scrollY className={`followup-detail ${modeClass}`}>
|
||||||
<View className='section'>
|
<View className='section'>
|
||||||
<View className='task-header'>
|
<View className='task-header'>
|
||||||
<Text className='task-header__title'>随访详情</Text>
|
<Text className='task-header__title'>随访详情</Text>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
.tab {
|
.tab {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 24px 16px;
|
padding: 24px 16px;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
padding: 20px 28px;
|
padding: 20px 28px;
|
||||||
|
|
||||||
text {
|
text {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,19 +75,19 @@
|
|||||||
|
|
||||||
&__type {
|
&__type {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__status {
|
&__status {
|
||||||
@include tag(transparent, $tx2);
|
@include tag(transparent, $tx2);
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__patient {
|
&__patient {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__date {
|
&__date {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,10 @@ import Taro, { useRouter } from '@tarojs/taro';
|
|||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import EmptyState from '@/components/EmptyState';
|
import EmptyState from '@/components/EmptyState';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
|
import { getStatusInlineStyle, getStatusLabel } from '@/utils/statusTag';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const STATUS_MAP: Record<string, { label: string; color: string }> = {
|
|
||||||
pending: { label: '待处理', color: '#f59e0b' },
|
|
||||||
in_progress: { label: '进行中', color: '#0891b2' },
|
|
||||||
completed: { label: '已完成', color: '#10b981' },
|
|
||||||
overdue: { label: '已逾期', color: '#ef4444' },
|
|
||||||
cancelled: { label: '已取消', color: '#94a3b8' },
|
|
||||||
};
|
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{ key: '', label: '全部' },
|
{ key: '', label: '全部' },
|
||||||
{ key: 'pending', label: '待处理' },
|
{ key: 'pending', label: '待处理' },
|
||||||
@@ -25,6 +19,7 @@ const TABS = [
|
|||||||
export default function FollowUpList() {
|
export default function FollowUpList() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const patientId = router.params.patientId || '';
|
const patientId = router.params.patientId || '';
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [tasks, setTasks] = useState<doctorApi.FollowUpTask[]>([]);
|
const [tasks, setTasks] = useState<doctorApi.FollowUpTask[]>([]);
|
||||||
const [activeTab, setActiveTab] = useState('');
|
const [activeTab, setActiveTab] = useState('');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -69,7 +64,7 @@ export default function FollowUpList() {
|
|||||||
if (loading && tasks.length === 0) return <Loading />;
|
if (loading && tasks.length === 0) return <Loading />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='followup-page'>
|
<ScrollView scrollY className={`followup-page ${modeClass}`}>
|
||||||
<View className='tabs'>
|
<View className='tabs'>
|
||||||
{TABS.map((t) => (
|
{TABS.map((t) => (
|
||||||
<View
|
<View
|
||||||
@@ -91,7 +86,6 @@ export default function FollowUpList() {
|
|||||||
) : (
|
) : (
|
||||||
<View className='task-list'>
|
<View className='task-list'>
|
||||||
{tasks.map((task) => {
|
{tasks.map((task) => {
|
||||||
const st = STATUS_MAP[task.status] || { label: task.status, color: '#94a3b8' };
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
key={task.id}
|
key={task.id}
|
||||||
@@ -100,8 +94,8 @@ export default function FollowUpList() {
|
|||||||
>
|
>
|
||||||
<View className='task-card__header'>
|
<View className='task-card__header'>
|
||||||
<Text className='task-card__type'>{getTypeLabel(task.follow_up_type)}</Text>
|
<Text className='task-card__type'>{getTypeLabel(task.follow_up_type)}</Text>
|
||||||
<View className='task-card__status' style={`background: ${st.color}20; color: ${st.color}`}>
|
<View className='task-card__status' style={getStatusInlineStyle(task.status)}>
|
||||||
<Text>{st.label}</Text>
|
<Text>{getStatusLabel(task.status)}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Text className='task-card__patient'>{task.patient_name || '未知患者'}</Text>
|
<Text className='task-card__patient'>{task.patient_name || '未知患者'}</Text>
|
||||||
|
|||||||
@@ -13,19 +13,18 @@
|
|||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
@include section-title;
|
@include section-title;
|
||||||
font-size: 40px;
|
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__greeting {
|
&__greeting {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__date {
|
&__date {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,34 +33,34 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 16px 24px;
|
margin: 16px 24px;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
background: #FEF2F2;
|
background: $dan-l;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
border-left: 4px solid #EF4444;
|
border-left: 4px solid $dan;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__alert-icon {
|
&__alert-icon {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #EF4444;
|
background: $dan;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__alert-text {
|
&__alert-text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: #991B1B;
|
color: $dan;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__alert-link {
|
&__alert-link {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: #EF4444;
|
color: $dan;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,11 +69,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__search-input {
|
&__search-input {
|
||||||
background: #F1F5F9;
|
background: $surface-alt;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: #94A3B8;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__section {
|
&__section {
|
||||||
@@ -113,14 +112,14 @@
|
|||||||
background: $pri-l;
|
background: $pri-l;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__card-num {
|
&__card-num {
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-size: 48px;
|
font-size: var(--tk-font-hero);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -128,7 +127,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__card-label {
|
&__card-label {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +144,7 @@
|
|||||||
|
|
||||||
&__logout {
|
&__logout {
|
||||||
color: $dan;
|
color: $dan;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-h2);
|
||||||
padding: 16px 48px;
|
padding: 16px 48px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@@ -172,7 +171,7 @@
|
|||||||
background: $acc-l;
|
background: $acc-l;
|
||||||
color: $acc;
|
color: $acc;
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,14 +191,14 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
background: $dan;
|
background: $dan;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 18px;
|
font-size: var(--tk-font-body-sm);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
border-radius: $r-pill;
|
border-radius: $r-pill;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { View, Text, Input, ScrollView } from '@tarojs/components';
|
import { View, Text, Input, ScrollView } from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
|
import { useElderClass } from '../../hooks/useElderClass';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
@@ -11,35 +12,67 @@ interface CardConfig {
|
|||||||
label: string;
|
label: string;
|
||||||
initial: string;
|
initial: string;
|
||||||
route: string;
|
route: string;
|
||||||
|
roles?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const CARDS: CardConfig[] = [
|
const ALL_CARDS: CardConfig[] = [
|
||||||
{ key: 'total_patients', label: '我的患者', initial: '患', route: '/pages/doctor/patients/index' },
|
{ key: 'total_patients', label: '我的患者', initial: '患', route: '/pages/doctor/patients/index' },
|
||||||
{ key: 'unread_messages', label: '未读消息', initial: '消', route: '/pages/doctor/consultation/index' },
|
{ key: 'unread_messages', label: '未读消息', initial: '消', route: '/pages/doctor/consultation/index' },
|
||||||
{ key: 'pending_follow_ups', label: '待处理随访', initial: '随', route: '/pages/doctor/followup/index' },
|
{ key: 'pending_follow_ups', label: '待处理随访', initial: '随', route: '/pages/doctor/followup/index', roles: ['doctor', 'nurse', 'health_manager'] },
|
||||||
{ key: 'today_consultations', label: '今日咨询', initial: '诊', route: '/pages/doctor/consultation/index' },
|
{ key: 'today_consultations', label: '今日咨询', initial: '诊', route: '/pages/doctor/consultation/index', roles: ['doctor', 'health_manager'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
const HEALTH_CARDS: CardConfig[] = [
|
const ALL_HEALTH_CARDS: CardConfig[] = [
|
||||||
{ key: 'pending_lab_review', label: '待审化验', initial: '化', route: '/pages/doctor/report/index' },
|
{ key: 'pending_lab_review', label: '待审化验', initial: '化', route: '/pages/doctor/report/index', roles: ['doctor'] },
|
||||||
{ key: 'today_appointments', label: '今日预约', initial: '约', route: '/pages/doctor/patients/index' },
|
{ key: 'today_appointments', label: '今日预约', initial: '约', route: '/pages/doctor/patients/index' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const QUICK_ACTIONS = [
|
interface QuickAction {
|
||||||
{ label: '化验审核', initial: '审', route: '/pages/doctor/report/index' },
|
label: string;
|
||||||
{ label: '患者查询', initial: '查', route: '/pages/doctor/patients/index' },
|
initial: string;
|
||||||
{ label: '随访记录', initial: '随', route: '/pages/doctor/followup/index' },
|
route: string;
|
||||||
{ label: '告警中心', initial: '警', route: '/pages/doctor/alerts/index' },
|
roles: string[];
|
||||||
{ label: '透析记录', initial: '透', route: '/pages/doctor/dialysis/index' },
|
}
|
||||||
{ label: '透析处方', initial: '方', route: '/pages/doctor/prescription/index' },
|
|
||||||
|
const ALL_QUICK_ACTIONS: QuickAction[] = [
|
||||||
|
{ label: '化验审核', initial: '审', route: '/pages/doctor/report/index', roles: ['doctor'] },
|
||||||
|
{ label: '患者查询', initial: '查', route: '/pages/doctor/patients/index', roles: ['doctor', 'nurse', 'health_manager'] },
|
||||||
|
{ label: '随访记录', initial: '随', route: '/pages/doctor/followup/index', roles: ['doctor', 'nurse', 'health_manager'] },
|
||||||
|
{ label: '告警中心', initial: '警', route: '/pages/doctor/alerts/index', roles: ['doctor', 'nurse', 'health_manager'] },
|
||||||
|
{ label: '透析管理', initial: '透', route: '/pages/doctor/dialysis/index', roles: ['doctor'] },
|
||||||
|
{ label: '处方管理', initial: '方', route: '/pages/doctor/prescription/index', roles: ['doctor'] },
|
||||||
|
{ label: '行动收件箱', initial: '行', route: '/pages/doctor/action-inbox/index', roles: ['doctor', 'nurse', 'health_manager'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const ROLE_LABELS: Record<string, string> = {
|
||||||
|
doctor: '医生',
|
||||||
|
nurse: '护士',
|
||||||
|
health_manager: '健康管理师',
|
||||||
|
admin: '管理员',
|
||||||
|
operator: '运营',
|
||||||
|
};
|
||||||
|
|
||||||
export default function DoctorHome() {
|
export default function DoctorHome() {
|
||||||
const { user, logout } = useAuthStore();
|
const { user, logout, roles } = useAuthStore();
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [dashboard, setDashboard] = useState<doctorApi.DoctorDashboard | null>(null);
|
const [dashboard, setDashboard] = useState<doctorApi.DoctorDashboard | null>(null);
|
||||||
const [alertCount, setAlertCount] = useState(0);
|
const [alertCount, setAlertCount] = useState(0);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const hasRole = (allowed: string[] | undefined) => {
|
||||||
|
if (!allowed) return true;
|
||||||
|
return roles.some((r) => r === 'admin' || allowed.includes(r));
|
||||||
|
};
|
||||||
|
|
||||||
|
const cards = useMemo(() => ALL_CARDS.filter((c) => hasRole(c.roles)), [roles]);
|
||||||
|
const healthCards = useMemo(() => ALL_HEALTH_CARDS.filter((c) => hasRole(c.roles)), [roles]);
|
||||||
|
const quickActions = useMemo(() => ALL_QUICK_ACTIONS.filter((a) => hasRole(a.roles)), [roles]);
|
||||||
|
|
||||||
|
const roleLabel = useMemo(() => {
|
||||||
|
const primary = roles.find((r) => r !== 'admin');
|
||||||
|
return primary ? (ROLE_LABELS[primary] || primary) : '医护';
|
||||||
|
}, [roles]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadDashboard();
|
loadDashboard();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -74,11 +107,11 @@ export default function DoctorHome() {
|
|||||||
if (loading) return <Loading />;
|
if (loading) return <Loading />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='doctor-home'>
|
<ScrollView scrollY className={`doctor-home ${modeClass}`}>
|
||||||
<View className='doctor-home__header'>
|
<View className='doctor-home__header'>
|
||||||
<Text className='doctor-home__title'>医护工作台</Text>
|
<Text className='doctor-home__title'>医护工作台</Text>
|
||||||
<Text className='doctor-home__greeting'>
|
<Text className='doctor-home__greeting'>
|
||||||
{user?.display_name || user?.username || '医生'},您好
|
{user?.display_name || user?.username || roleLabel},您好
|
||||||
</Text>
|
</Text>
|
||||||
<Text className='doctor-home__date'>
|
<Text className='doctor-home__date'>
|
||||||
{new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'numeric', weekday: 'long' })}
|
{new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'numeric', weekday: 'long' })}
|
||||||
@@ -104,7 +137,7 @@ export default function DoctorHome() {
|
|||||||
<View className='doctor-home__section'>
|
<View className='doctor-home__section'>
|
||||||
<Text className='doctor-home__section-title'>工作概览</Text>
|
<Text className='doctor-home__section-title'>工作概览</Text>
|
||||||
<View className='doctor-home__grid'>
|
<View className='doctor-home__grid'>
|
||||||
{CARDS.map((card) => (
|
{cards.map((card) => (
|
||||||
<View
|
<View
|
||||||
key={card.key}
|
key={card.key}
|
||||||
className='doctor-home__card'
|
className='doctor-home__card'
|
||||||
@@ -118,10 +151,10 @@ export default function DoctorHome() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className='doctor-home__section'>
|
{healthCards.length > 0 && (<View className='doctor-home__section'>
|
||||||
<Text className='doctor-home__section-title'>健康审核</Text>
|
<Text className='doctor-home__section-title'>健康审核</Text>
|
||||||
<View className='doctor-home__grid'>
|
<View className='doctor-home__grid'>
|
||||||
{HEALTH_CARDS.map((card) => (
|
{healthCards.map((card) => (
|
||||||
<View
|
<View
|
||||||
key={card.key}
|
key={card.key}
|
||||||
className='doctor-home__card'
|
className='doctor-home__card'
|
||||||
@@ -133,12 +166,12 @@ export default function DoctorHome() {
|
|||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>)}
|
||||||
|
|
||||||
<View className='doctor-home__section'>
|
<View className='doctor-home__section'>
|
||||||
<Text className='doctor-home__section-title'>快捷操作</Text>
|
<Text className='doctor-home__section-title'>快捷操作</Text>
|
||||||
<View className='doctor-home__quick-actions'>
|
<View className='doctor-home__quick-actions'>
|
||||||
{QUICK_ACTIONS.map((action) => (
|
{quickActions.map((action) => (
|
||||||
<View
|
<View
|
||||||
key={action.route}
|
key={action.route}
|
||||||
className='quick-action'
|
className='quick-action'
|
||||||
|
|||||||
@@ -33,12 +33,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-label {
|
.info-label {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-value {
|
.info-value {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.warning-label {
|
.warning-label {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $wrn;
|
color: $wrn;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.warning-text {
|
.warning-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $pri-d;
|
color: $pri-d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,14 +68,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-block-label {
|
.info-block-label {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-block-text {
|
.info-block-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
.vital-value {
|
.vital-value {
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-size: 36px;
|
font-size: var(--tk-font-num-lg);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vital-label {
|
.vital-label {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,13 +116,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-value {
|
.stat-value {
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
|
|
||||||
@@ -151,18 +151,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__type {
|
&__type {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__date {
|
&__date {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__abnormal {
|
&__abnormal {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $dan;
|
color: $dan;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
background: $pri;
|
background: $pri;
|
||||||
color: $card;
|
color: $card;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -196,5 +196,5 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 80px 32px;
|
padding: 80px 32px;
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { View, Text, ScrollView } from '@tarojs/components';
|
|||||||
import Taro, { useRouter } from '@tarojs/taro';
|
import Taro, { useRouter } from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '../../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
export default function PatientDetail() {
|
export default function PatientDetail() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const patientId = router.params.id || '';
|
const patientId = router.params.id || '';
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [patient, setPatient] = useState<doctorApi.PatientDetail | null>(null);
|
const [patient, setPatient] = useState<doctorApi.PatientDetail | null>(null);
|
||||||
const [summary, setSummary] = useState<doctorApi.HealthSummary | null>(null);
|
const [summary, setSummary] = useState<doctorApi.HealthSummary | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -40,10 +42,10 @@ export default function PatientDetail() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (loading) return <Loading />;
|
if (loading) return <Loading />;
|
||||||
if (!patient) return <View className='error-text'><Text>患者信息加载失败</Text></View>;
|
if (!patient) return <View className={`error-text ${modeClass}`}><Text>患者信息加载失败</Text></View>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='patient-detail'>
|
<ScrollView scrollY className={`patient-detail ${modeClass}`}>
|
||||||
{/* 基本信息 */}
|
{/* 基本信息 */}
|
||||||
<View className='section'>
|
<View className='section'>
|
||||||
<View className='section-header'>
|
<View className='section-header'>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
background: $card;
|
background: $card;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
padding: 20px 24px;
|
padding: 20px 24px;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: $shadow-sm;
|
box-shadow: $shadow-sm;
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
padding: 10px 24px;
|
padding: 10px 24px;
|
||||||
border-radius: $r-pill;
|
border-radius: $r-pill;
|
||||||
background: $bd-l;
|
background: $bd-l;
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
text {
|
text {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,14 +75,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
&__name {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__meta {
|
&__meta {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
background: $pri-l;
|
background: $pri-l;
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
|
|
||||||
&__btn {
|
&__btn {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { View, Text, Input, ScrollView } from '@tarojs/components';
|
import { View, Text, Input, ScrollView } from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro, { usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import EmptyState from '@/components/EmptyState';
|
import EmptyState from '@/components/EmptyState';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
export default function PatientList() {
|
export default function PatientList() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [patients, setPatients] = useState<doctorApi.PatientItem[]>([]);
|
const [patients, setPatients] = useState<doctorApi.PatientItem[]>([]);
|
||||||
const [tags, setTags] = useState<doctorApi.PatientTag[]>([]);
|
const [tags, setTags] = useState<doctorApi.PatientTag[]>([]);
|
||||||
const [activeTag, setActiveTag] = useState<string>('');
|
const [activeTag, setActiveTag] = useState<string>('');
|
||||||
@@ -14,14 +16,15 @@ export default function PatientList() {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
const loadingRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadTags();
|
loadTags();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadPatients();
|
loadPatients(1, true);
|
||||||
}, [page, activeTag]);
|
}, [activeTag]);
|
||||||
|
|
||||||
const loadTags = async () => {
|
const loadTags = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -30,32 +33,51 @@ export default function PatientList() {
|
|||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadPatients = async () => {
|
const loadPatients = async (pageNum: number, isRefresh = false) => {
|
||||||
setLoading(true);
|
if (loadingRef.current) return;
|
||||||
|
loadingRef.current = true;
|
||||||
|
if (isRefresh) setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await doctorApi.listPatients({
|
const res = await doctorApi.listPatients({
|
||||||
page,
|
page: pageNum,
|
||||||
page_size: 20,
|
page_size: 20,
|
||||||
search: search || undefined,
|
search: search || undefined,
|
||||||
tag_id: activeTag || undefined,
|
tag_id: activeTag || undefined,
|
||||||
});
|
});
|
||||||
setPatients(res.data || []);
|
const list = res.data || [];
|
||||||
|
if (isRefresh) {
|
||||||
|
setPatients(list);
|
||||||
|
} else {
|
||||||
|
setPatients((prev) => [...prev, ...list]);
|
||||||
|
}
|
||||||
setTotal(res.total || 0);
|
setTotal(res.total || 0);
|
||||||
|
setPage(pageNum);
|
||||||
} catch {
|
} catch {
|
||||||
Taro.showToast({ title: '加载失败', icon: 'none' });
|
Taro.showToast({ title: '加载失败', icon: 'none' });
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
loadingRef.current = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
usePullDownRefresh(() => {
|
||||||
|
loadPatients(1, true).finally(() => {
|
||||||
|
Taro.stopPullDownRefresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
useReachBottom(() => {
|
||||||
|
if (!loading && patients.length < total) {
|
||||||
|
loadPatients(page + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
setPage(1);
|
loadPatients(1, true);
|
||||||
loadPatients();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTagFilter = (tagId: string) => {
|
const handleTagFilter = (tagId: string) => {
|
||||||
setActiveTag(tagId === activeTag ? '' : tagId);
|
setActiveTag(tagId === activeTag ? '' : tagId);
|
||||||
setPage(1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getGenderLabel = (gender?: string) => {
|
const getGenderLabel = (gender?: string) => {
|
||||||
@@ -78,7 +100,7 @@ export default function PatientList() {
|
|||||||
if (loading && patients.length === 0) return <Loading />;
|
if (loading && patients.length === 0) return <Loading />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='patient-list-page'>
|
<ScrollView scrollY className={`patient-list-page ${modeClass}`}>
|
||||||
<View className='search-bar'>
|
<View className='search-bar'>
|
||||||
<Input
|
<Input
|
||||||
className='search-input'
|
className='search-input'
|
||||||
@@ -154,23 +176,12 @@ export default function PatientList() {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{total > 20 && (
|
{!loading && patients.length >= total && total > 0 && (
|
||||||
<View className='pagination'>
|
<View style={{ textAlign: 'center', padding: '20px' }}>
|
||||||
<Text
|
<Text style={{ fontSize: '24px', color: '#78716C' }}>没有更多了</Text>
|
||||||
className={`pagination__btn ${page <= 1 ? 'disabled' : ''}`}
|
|
||||||
onClick={() => page > 1 && setPage(page - 1)}
|
|
||||||
>
|
|
||||||
上一页
|
|
||||||
</Text>
|
|
||||||
<Text className='pagination__info'>{page} / {Math.ceil(total / 20)}</Text>
|
|
||||||
<Text
|
|
||||||
className={`pagination__btn ${page >= Math.ceil(total / 20) ? 'disabled' : ''}`}
|
|
||||||
onClick={() => page < Math.ceil(total / 20) && setPage(page + 1)}
|
|
||||||
>
|
|
||||||
下一页
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
{loading && patients.length > 0 && <Loading />}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import '../../../../styles/variables.scss';
|
@import '../../../../styles/variables.scss';
|
||||||
|
@import '../../../../styles/mixins.scss';
|
||||||
|
|
||||||
.create-page {
|
.create-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -36,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-label {
|
.form-label {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
@@ -45,12 +46,12 @@
|
|||||||
.form-input {
|
.form-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-value {
|
.form-value {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
|
|
||||||
&.placeholder {
|
&.placeholder {
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
.form-textarea {
|
.form-textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
min-height: 120px;
|
min-height: 120px;
|
||||||
background: $bg;
|
background: $bg;
|
||||||
@@ -86,7 +87,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.submit-btn__text {
|
.submit-btn__text {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { View, Text, Input, Textarea, Picker, ScrollView } from '@tarojs/compone
|
|||||||
import Taro, { useRouter } from '@tarojs/taro';
|
import Taro, { useRouter } from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '../../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
interface FormState {
|
interface FormState {
|
||||||
@@ -50,6 +51,7 @@ const initialForm: FormState = {
|
|||||||
export default function PrescriptionCreate() {
|
export default function PrescriptionCreate() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const patientId = router.params.patientId || '';
|
const patientId = router.params.patientId || '';
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [form, setForm] = useState<FormState>(initialForm);
|
const [form, setForm] = useState<FormState>(initialForm);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
@@ -114,7 +116,7 @@ export default function PrescriptionCreate() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='create-page'>
|
<ScrollView scrollY className={`create-page ${modeClass}`}>
|
||||||
{/* 透析器 */}
|
{/* 透析器 */}
|
||||||
<View className='section'>
|
<View className='section'>
|
||||||
<Text className='section-title'>透析器</Text>
|
<Text className='section-title'>透析器</Text>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import '../../../../styles/variables.scss';
|
@import '../../../../styles/variables.scss';
|
||||||
|
@import '../../../../styles/mixins.scss';
|
||||||
|
|
||||||
.prescription-detail {
|
.prescription-detail {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -31,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rx-header__title {
|
.rx-header__title {
|
||||||
font-size: 34px;
|
font-size: var(--tk-font-num-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
background: $bd-l;
|
background: $bd-l;
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rx-sub {
|
.rx-sub {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
display: block;
|
display: block;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
@@ -69,12 +70,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-label {
|
.detail-label {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-value {
|
.detail-value {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -83,7 +84,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.notes-text {
|
.notes-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
@@ -91,7 +92,7 @@
|
|||||||
.error-text {
|
.error-text {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 120px 0;
|
padding: 120px 0;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +132,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-btn__text {
|
.action-btn__text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { View, Text, ScrollView } from '@tarojs/components';
|
|||||||
import Taro, { useRouter } from '@tarojs/taro';
|
import Taro, { useRouter } from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '../../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
export default function PrescriptionDetail() {
|
export default function PrescriptionDetail() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const id = router.params.id || '';
|
const id = router.params.id || '';
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [rx, setRx] = useState<doctorApi.DialysisPrescription | null>(null);
|
const [rx, setRx] = useState<doctorApi.DialysisPrescription | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
@@ -76,10 +78,10 @@ export default function PrescriptionDetail() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (loading) return <Loading />;
|
if (loading) return <Loading />;
|
||||||
if (!rx) return <View className='error-text'><Text>处方加载失败</Text></View>;
|
if (!rx) return <View className={`error-text ${modeClass}`}><Text>处方加载失败</Text></View>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='prescription-detail'>
|
<ScrollView scrollY className={`prescription-detail ${modeClass}`}>
|
||||||
{/* 状态头部 */}
|
{/* 状态头部 */}
|
||||||
<View className='section'>
|
<View className='section'>
|
||||||
<View className='rx-header'>
|
<View className='rx-header'>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import '../../../styles/variables.scss';
|
@import '../../../styles/variables.scss';
|
||||||
|
@import '../../../styles/mixins.scss';
|
||||||
|
|
||||||
.prescription-page {
|
.prescription-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -15,7 +16,7 @@
|
|||||||
background: $bg;
|
background: $bg;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab-text {
|
.tab-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prescription-count {
|
.prescription-count {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
padding: 8px 0 16px;
|
padding: 8px 0 16px;
|
||||||
}
|
}
|
||||||
@@ -86,7 +87,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prescription-card__model {
|
.prescription-card__model {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-body);
|
||||||
background: $bd-l;
|
background: $bd-l;
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
|
|
||||||
@@ -112,13 +113,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prescription-card__meta {
|
.prescription-card__meta {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prescription-card__date {
|
.prescription-card__date {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
display: block;
|
display: block;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
background: $card;
|
background: $card;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
|
|
||||||
&--disabled {
|
&--disabled {
|
||||||
@@ -145,7 +146,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page-info {
|
.page-info {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +170,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fab-text {
|
.fab-text {
|
||||||
font-size: 40px;
|
font-size: var(--tk-font-hero);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Taro, { useRouter } from '@tarojs/taro';
|
|||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import EmptyState from '@/components/EmptyState';
|
import EmptyState from '@/components/EmptyState';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
@@ -15,6 +16,7 @@ const TABS = [
|
|||||||
export default function PrescriptionList() {
|
export default function PrescriptionList() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const patientId = router.params.patientId || '';
|
const patientId = router.params.patientId || '';
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [searchPatient, setSearchPatient] = useState('');
|
const [searchPatient, setSearchPatient] = useState('');
|
||||||
const [currentPatientId, setCurrentPatientId] = useState(patientId);
|
const [currentPatientId, setCurrentPatientId] = useState(patientId);
|
||||||
const [activeTab, setActiveTab] = useState('');
|
const [activeTab, setActiveTab] = useState('');
|
||||||
@@ -66,7 +68,7 @@ export default function PrescriptionList() {
|
|||||||
if (loading && prescriptions.length === 0) return <Loading />;
|
if (loading && prescriptions.length === 0) return <Loading />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='prescription-page'>
|
<ScrollView scrollY className={`prescription-page ${modeClass}`}>
|
||||||
{!patientId && (
|
{!patientId && (
|
||||||
<View className='search-bar'>
|
<View className='search-bar'>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -28,13 +28,13 @@
|
|||||||
|
|
||||||
&__type {
|
&__type {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 32px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__status {
|
&__status {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
padding: 6px 16px;
|
padding: 6px 16px;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -45,13 +45,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.report-date {
|
.report-date {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.review-info {
|
.review-info {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $acc;
|
color: $acc;
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.indicator-cell {
|
.indicator-cell {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
|
|
||||||
&--name {
|
&--name {
|
||||||
flex: 2;
|
flex: 2;
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.indicator-row--header & {
|
.indicator-row--header & {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.notes-text {
|
.notes-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
background: $bd-l;
|
background: $bd-l;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__text {
|
&__text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $card;
|
color: $card;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -168,5 +168,5 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 80px 32px;
|
padding: 80px 32px;
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { View, Text, Textarea, ScrollView } from '@tarojs/components';
|
|||||||
import Taro, { useRouter } from '@tarojs/taro';
|
import Taro, { useRouter } from '@tarojs/taro';
|
||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '../../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
export default function ReportDetail() {
|
export default function ReportDetail() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const patientId = router.params.patientId || '';
|
const patientId = router.params.patientId || '';
|
||||||
const reportId = router.params.id || '';
|
const reportId = router.params.id || '';
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [report, setReport] = useState<doctorApi.LabReportDetail | null>(null);
|
const [report, setReport] = useState<doctorApi.LabReportDetail | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [doctorNotes, setDoctorNotes] = useState('');
|
const [doctorNotes, setDoctorNotes] = useState('');
|
||||||
@@ -51,10 +53,10 @@ export default function ReportDetail() {
|
|||||||
const formatDate = (d: string) => new Date(d).toLocaleDateString('zh-CN');
|
const formatDate = (d: string) => new Date(d).toLocaleDateString('zh-CN');
|
||||||
|
|
||||||
if (loading) return <Loading />;
|
if (loading) return <Loading />;
|
||||||
if (!report) return <View className='error-text'><Text>报告加载失败</Text></View>;
|
if (!report) return <View className={`error-text ${modeClass}`}><Text>报告加载失败</Text></View>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='report-detail'>
|
<ScrollView scrollY className={`report-detail ${modeClass}`}>
|
||||||
{/* 基本信息 */}
|
{/* 基本信息 */}
|
||||||
<View className='section'>
|
<View className='section'>
|
||||||
<View className='report-header'>
|
<View className='report-header'>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
background: $card;
|
background: $card;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
padding: 20px 24px;
|
padding: 20px 24px;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: $shadow-sm;
|
box-shadow: $shadow-sm;
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
text {
|
text {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,13 +56,13 @@
|
|||||||
|
|
||||||
&__type {
|
&__type {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__date {
|
&__date {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,13 +73,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__abnormal {
|
&__abnormal {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $dan;
|
color: $dan;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__normal {
|
&__normal {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $acc;
|
color: $acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import Taro, { useRouter } from '@tarojs/taro';
|
|||||||
import * as doctorApi from '@/services/doctor';
|
import * as doctorApi from '@/services/doctor';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import EmptyState from '@/components/EmptyState';
|
import EmptyState from '@/components/EmptyState';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
export default function ReportList() {
|
export default function ReportList() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const patientId = router.params.patientId || '';
|
const patientId = router.params.patientId || '';
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [searchPatient, setSearchPatient] = useState('');
|
const [searchPatient, setSearchPatient] = useState('');
|
||||||
const [currentPatientId, setCurrentPatientId] = useState(patientId);
|
const [currentPatientId, setCurrentPatientId] = useState(patientId);
|
||||||
const [reports, setReports] = useState<doctorApi.LabReportItem[]>([]);
|
const [reports, setReports] = useState<doctorApi.LabReportItem[]>([]);
|
||||||
@@ -55,7 +57,7 @@ export default function ReportList() {
|
|||||||
if (loading && reports.length === 0) return <Loading />;
|
if (loading && reports.length === 0) return <Loading />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='report-page'>
|
<ScrollView scrollY className={`report-page ${modeClass}`}>
|
||||||
{!patientId && (
|
{!patientId && (
|
||||||
<View className='search-bar'>
|
<View className='search-bar'>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -1,34 +1,5 @@
|
|||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
@import '../../styles/mixins.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 {
|
.events-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -43,14 +14,14 @@
|
|||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 40px;
|
font-size: var(--tk-font-h1);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__subtitle {
|
&__subtitle {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +48,7 @@
|
|||||||
|
|
||||||
&__status {
|
&__status {
|
||||||
@include tag($bd-l, $tx2);
|
@include tag($bd-l, $tx2);
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__status--published {
|
&__status--published {
|
||||||
@@ -97,7 +68,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__points {
|
&__points {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $wrn;
|
color: $wrn;
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
@@ -105,7 +76,7 @@
|
|||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-h1);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -113,7 +84,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__desc {
|
&__desc {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@@ -128,13 +99,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__date {
|
&__date {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__location {
|
&__location {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
@@ -146,8 +117,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__participants {
|
&__participants {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +132,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-text {
|
&-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $card;
|
color: $card;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Taro from '@tarojs/taro';
|
|||||||
import * as pointsApi from '@/services/points';
|
import * as pointsApi from '@/services/points';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import EmptyState from '@/components/EmptyState';
|
import EmptyState from '@/components/EmptyState';
|
||||||
|
import { useElderClass } from '../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const STATUS_MAP: Record<string, { label: string; className: string }> = {
|
const STATUS_MAP: Record<string, { label: string; className: string }> = {
|
||||||
@@ -14,6 +15,7 @@ const STATUS_MAP: Record<string, { label: string; className: string }> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function EventsPage() {
|
export default function EventsPage() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [events, setEvents] = useState<pointsApi.OfflineEvent[]>([]);
|
const [events, setEvents] = useState<pointsApi.OfflineEvent[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [registering, setRegistering] = useState<string | null>(null);
|
const [registering, setRegistering] = useState<string | null>(null);
|
||||||
@@ -59,7 +61,7 @@ export default function EventsPage() {
|
|||||||
if (loading) return <Loading />;
|
if (loading) return <Loading />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='events-page'>
|
<ScrollView scrollY className={`events-page ${modeClass}`}>
|
||||||
<View className='events-header'>
|
<View className='events-header'>
|
||||||
<Text className='events-header__title'>线下活动</Text>
|
<Text className='events-header__title'>线下活动</Text>
|
||||||
<Text className='events-header__subtitle'>参加活动赢取积分</Text>
|
<Text className='events-header__subtitle'>参加活动赢取积分</Text>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
.detail-title {
|
.detail-title {
|
||||||
@include section-title;
|
@include section-title;
|
||||||
font-size: 34px;
|
font-size: var(--tk-font-num-lg);
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,12 +34,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-label {
|
.detail-label {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-value {
|
.detail-value {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
|
|
||||||
&.status-completed { color: $acc; }
|
&.status-completed { color: $acc; }
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.countdown-text {
|
.countdown-text {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $wrn;
|
color: $wrn;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-desc-text {
|
.detail-desc-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
.submit-textarea {
|
.submit-textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
background: $bg;
|
background: $bg;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.submit-btn-text {
|
.submit-btn-text {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -134,6 +134,6 @@
|
|||||||
|
|
||||||
.loading-text,
|
.loading-text,
|
||||||
.empty-text {
|
.empty-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import { TEMPLATE_IDS } from '@/services/wechat-templates';
|
|||||||
import { trackEvent } from '@/services/analytics';
|
import { trackEvent } from '@/services/analytics';
|
||||||
import Loading from '../../../components/Loading';
|
import Loading from '../../../components/Loading';
|
||||||
import ErrorState from '../../../components/ErrorState';
|
import ErrorState from '../../../components/ErrorState';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
export default function FollowUpDetail() {
|
export default function FollowUpDetail() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const id = router.params.id || '';
|
const id = router.params.id || '';
|
||||||
|
|
||||||
@@ -82,7 +84,7 @@ export default function FollowUpDetail() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<View className='detail-page'>
|
<View className={`detail-page ${modeClass}`}>
|
||||||
<Loading />
|
<Loading />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -90,7 +92,7 @@ export default function FollowUpDetail() {
|
|||||||
|
|
||||||
if (error || !task) {
|
if (error || !task) {
|
||||||
return (
|
return (
|
||||||
<View className='detail-page'>
|
<View className={`detail-page ${modeClass}`}>
|
||||||
<ErrorState text='任务不存在' />
|
<ErrorState text='任务不存在' />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -99,7 +101,7 @@ export default function FollowUpDetail() {
|
|||||||
const isCompleted = task.status === 'completed';
|
const isCompleted = task.status === 'completed';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='detail-page'>
|
<View className={`detail-page ${modeClass}`}>
|
||||||
<View className='detail-card'>
|
<View className='detail-card'>
|
||||||
<Text className='detail-title'>{task.follow_up_type}</Text>
|
<Text className='detail-title'>{task.follow_up_type}</Text>
|
||||||
<View className='detail-row'>
|
<View className='detail-row'>
|
||||||
|
|||||||
@@ -4,17 +4,18 @@
|
|||||||
.health-page {
|
.health-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: $bg;
|
background: $bg;
|
||||||
padding: 20px 16px 100px;
|
padding: 20px 24px 100px;
|
||||||
padding-bottom: calc(100px + env(safe-area-inset-bottom));
|
padding-bottom: calc(100px + env(safe-area-inset-bottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 页头 ─── */
|
/* ─── 页头 ─── */
|
||||||
.health-header {
|
.health-header {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.health-title {
|
.health-title {
|
||||||
font-size: 22px;
|
@include serif-number;
|
||||||
|
font-size: var(--tk-font-h1);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
|
|
||||||
&.vital-tab-active {
|
&.vital-tab-active {
|
||||||
background: $pri;
|
background: $pri;
|
||||||
|
box-shadow: 0 2px 8px rgba(196, 98, 58, 0.25);
|
||||||
|
|
||||||
.vital-tab-text {
|
.vital-tab-text {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -48,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vital-tab-text {
|
.vital-tab-text {
|
||||||
font-size: 15px;
|
font-size: var(--tk-font-cap);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
@@ -65,11 +67,11 @@
|
|||||||
|
|
||||||
/* ─── 录入区 ─── */
|
/* ─── 录入区 ─── */
|
||||||
.input-section {
|
.input-section {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 20px;
|
||||||
background: $card;
|
background: $card;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
padding: 16px;
|
padding: 20px;
|
||||||
box-shadow: $shadow-sm;
|
box-shadow: $shadow-md;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group {
|
.input-group {
|
||||||
@@ -77,8 +79,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-label {
|
.input-label {
|
||||||
font-size: 13px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
@@ -90,7 +92,7 @@
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -98,13 +100,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-ref {
|
.input-ref {
|
||||||
font-size: 13px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-label--secondary {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── 血糖时段选择 ─── */
|
/* ─── 血糖时段选择 ─── */
|
||||||
.period-group {
|
.period-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -133,7 +139,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.period-btn-text {
|
.period-btn-text {
|
||||||
font-size: 15px;
|
font-size: var(--tk-font-cap);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
@@ -145,7 +151,8 @@
|
|||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
background: $pri;
|
background: $pri;
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
margin-top: 12px;
|
margin-top: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(196, 98, 58, 0.25);
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
@@ -153,7 +160,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.save-btn-text {
|
.save-btn-text {
|
||||||
font-size: 17px;
|
font-size: var(--tk-font-body-sm);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
@@ -176,7 +183,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.trend-empty-text {
|
.trend-empty-text {
|
||||||
font-size: 14px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +202,25 @@
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 12px 8px;
|
padding: 12px 8px;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-threshold-line {
|
||||||
|
position: absolute;
|
||||||
|
left: 8px;
|
||||||
|
right: 8px;
|
||||||
|
border-top: 1.5px dashed $wrn;
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trend-threshold-label {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: -16px;
|
||||||
|
font-size: var(--tk-font-micro);
|
||||||
|
color: $wrn;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trend-bar-col {
|
.trend-bar-col {
|
||||||
@@ -222,8 +248,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.trend-bar-label {
|
.trend-bar-label {
|
||||||
font-size: 11px;
|
font-size: var(--tk-font-micro);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +282,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.device-icon-text {
|
.device-icon-text {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-info {
|
.device-info {
|
||||||
@@ -265,21 +291,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.device-name {
|
.device-name {
|
||||||
font-size: 15px;
|
font-size: var(--tk-font-cap);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-desc {
|
.device-desc {
|
||||||
font-size: 13px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $acc;
|
color: $acc;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-arrow {
|
.device-arrow {
|
||||||
font-size: 14px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,37 +322,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.article-entry-text {
|
.article-entry-text {
|
||||||
font-size: 15px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── AI 建议卡片 ─── */
|
/* ─── AI 建议卡片 ─── */
|
||||||
.ai-suggestion-card {
|
.ai-suggestion-card {
|
||||||
background: $card;
|
background: $acc-l;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 20px;
|
||||||
box-shadow: $shadow-sm;
|
box-shadow: none;
|
||||||
border-left: 4px solid $pri;
|
border-left: 4px solid $acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-card-header {
|
.ai-card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-card-title {
|
.ai-card-title {
|
||||||
font-size: 15px;
|
font-size: var(--tk-font-cap);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-card-count {
|
.ai-card-count {
|
||||||
font-size: 12px;
|
font-size: var(--tk-font-micro);
|
||||||
color: $acc;
|
color: $acc;
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-suggestion-item {
|
.ai-suggestion-item {
|
||||||
@@ -344,6 +371,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ai-suggestion-text {
|
.ai-suggestion-text {
|
||||||
font-size: 13px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { View, Text, Input } from '@tarojs/components';
|
import { View, Text, Input } from '@tarojs/components';
|
||||||
import Taro, { useDidShow } from '@tarojs/taro';
|
import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro';
|
||||||
import { useHealthStore } from '../../stores/health';
|
import { useHealthStore } from '../../stores/health';
|
||||||
import { useAuthStore } from '../../stores/auth';
|
import { useAuthStore } from '../../stores/auth';
|
||||||
|
import { useElderClass } from '../../hooks/useElderClass';
|
||||||
import { inputVitalSign, getTrend, getHealthThresholds, findThreshold, DEFAULT_THRESHOLDS, type HealthThreshold } from '../../services/health';
|
import { inputVitalSign, getTrend, getHealthThresholds, findThreshold, DEFAULT_THRESHOLDS, type HealthThreshold } from '../../services/health';
|
||||||
import { listPendingSuggestions, type AiSuggestionItem } from '../../services/ai-analysis';
|
import { listPendingSuggestions, type AiSuggestionItem } from '../../services/ai-analysis';
|
||||||
import Loading from '../../components/Loading';
|
import Loading from '../../components/Loading';
|
||||||
|
import GuestGuard from '../../components/GuestGuard';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
type VitalType = 'blood_pressure' | 'heart_rate' | 'blood_sugar' | 'weight';
|
type VitalType = 'blood_pressure' | 'heart_rate' | 'blood_sugar' | 'weight';
|
||||||
@@ -40,7 +42,8 @@ interface TrendPoint {
|
|||||||
|
|
||||||
export default function Health() {
|
export default function Health() {
|
||||||
const { todaySummary, loading, refreshToday, getTrend: fetchTrend } = useHealthStore();
|
const { todaySummary, loading, refreshToday, getTrend: fetchTrend } = useHealthStore();
|
||||||
const { currentPatient } = useAuthStore();
|
const { user, currentPatient } = useAuthStore();
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [activeTab, setActiveTab] = useState<VitalType>('blood_pressure');
|
const [activeTab, setActiveTab] = useState<VitalType>('blood_pressure');
|
||||||
const [systolic, setSystolic] = useState('');
|
const [systolic, setSystolic] = useState('');
|
||||||
const [diastolic, setDiastolic] = useState('');
|
const [diastolic, setDiastolic] = useState('');
|
||||||
@@ -55,12 +58,24 @@ export default function Health() {
|
|||||||
const [thresholds, setThresholds] = useState<HealthThreshold[]>(DEFAULT_THRESHOLDS);
|
const [thresholds, setThresholds] = useState<HealthThreshold[]>(DEFAULT_THRESHOLDS);
|
||||||
|
|
||||||
useDidShow(() => {
|
useDidShow(() => {
|
||||||
|
if (!user) return;
|
||||||
refreshToday();
|
refreshToday();
|
||||||
loadTrend(activeTab);
|
loadTrend(activeTab);
|
||||||
loadAiSuggestions();
|
loadAiSuggestions();
|
||||||
getHealthThresholds().then((t) => { if (t.length > 0) setThresholds(t); });
|
getHealthThresholds().then((t) => { if (t.length > 0) setThresholds(t); });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
usePullDownRefresh(() => {
|
||||||
|
if (!user) return;
|
||||||
|
Promise.all([refreshToday(true), loadTrend(activeTab), loadAiSuggestions()]).finally(() => {
|
||||||
|
Taro.stopPullDownRefresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return <GuestGuard title='请先登录' desc='登录后即可记录和查看健康数据' />;
|
||||||
|
}
|
||||||
|
|
||||||
const loadAiSuggestions = async () => {
|
const loadAiSuggestions = async () => {
|
||||||
try {
|
try {
|
||||||
const items = await listPendingSuggestions();
|
const items = await listPendingSuggestions();
|
||||||
@@ -74,9 +89,9 @@ export default function Health() {
|
|||||||
setTrendLoading(true);
|
setTrendLoading(true);
|
||||||
try {
|
try {
|
||||||
const indicatorMap: Record<VitalType, string> = {
|
const indicatorMap: Record<VitalType, string> = {
|
||||||
blood_pressure: 'blood_pressure_systolic',
|
blood_pressure: 'systolic_bp_morning',
|
||||||
heart_rate: 'heart_rate',
|
heart_rate: 'heart_rate',
|
||||||
blood_sugar: 'blood_sugar_fasting',
|
blood_sugar: 'blood_sugar',
|
||||||
weight: 'weight',
|
weight: 'weight',
|
||||||
};
|
};
|
||||||
const points = await fetchTrend(indicatorMap[type], '7d');
|
const points = await fetchTrend(indicatorMap[type], '7d');
|
||||||
@@ -164,7 +179,8 @@ export default function Health() {
|
|||||||
case 'blood_sugar': {
|
case 'blood_sugar': {
|
||||||
const val = parseFloat(sugarVal);
|
const val = parseFloat(sugarVal);
|
||||||
if (!val) { Taro.showToast({ title: '请填写血糖值', icon: 'none' }); return; }
|
if (!val) { Taro.showToast({ title: '请填写血糖值', icon: 'none' }); return; }
|
||||||
await inputVitalSign(patientId, { indicator_type: 'blood_sugar', value: val });
|
const bsType = sugarPeriod === 'fasting' ? 'blood_sugar_fasting' : 'blood_sugar_postprandial';
|
||||||
|
await inputVitalSign(patientId, { indicator_type: bsType, value: val });
|
||||||
setSugarVal('');
|
setSugarVal('');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -187,10 +203,17 @@ export default function Health() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const maxTrendValue = Math.max(...trendData.map((d) => d.value), 1);
|
const maxTrendValue = Math.max(...trendData.map((d) => d.value), 1);
|
||||||
|
|
||||||
|
const getThresholdValue = (type: VitalType, th: HealthThreshold[]): number | null => {
|
||||||
|
if (type === 'blood_pressure') return findThreshold(th, 'systolic_bp', 'high')?.threshold_value ?? 140;
|
||||||
|
if (type === 'heart_rate') return findThreshold(th, 'heart_rate', 'high')?.threshold_value ?? 100;
|
||||||
|
if (type === 'blood_sugar') return findThreshold(th, 'blood_sugar_fasting', 'high')?.threshold_value ?? 6.1;
|
||||||
|
return null;
|
||||||
|
};
|
||||||
const dayLabels = ['日', '一', '二', '三', '四', '五', '六'];
|
const dayLabels = ['日', '一', '二', '三', '四', '五', '六'];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='health-page'>
|
<View className={`health-page ${modeClass}`}>
|
||||||
{/* 页头 */}
|
{/* 页头 */}
|
||||||
<View className='health-header'>
|
<View className='health-header'>
|
||||||
<Text className='health-title'>健康数据</Text>
|
<Text className='health-title'>健康数据</Text>
|
||||||
@@ -201,7 +224,7 @@ export default function Health() {
|
|||||||
<View className='ai-suggestion-card' onClick={() => {
|
<View className='ai-suggestion-card' onClick={() => {
|
||||||
const first = aiSuggestions[0];
|
const first = aiSuggestions[0];
|
||||||
if (first?.suggestion_type === 'appointment') {
|
if (first?.suggestion_type === 'appointment') {
|
||||||
Taro.navigateTo({ url: `/pages/pkg-appointment/create/index?patientId=${first.patient_id}` });
|
Taro.navigateTo({ url: `/pages/appointment/create/index` });
|
||||||
} else if (first?.suggestion_type === 'followup') {
|
} else if (first?.suggestion_type === 'followup') {
|
||||||
Taro.navigateTo({ url: '/pages/pkg-profile/followups/index' });
|
Taro.navigateTo({ url: '/pages/pkg-profile/followups/index' });
|
||||||
} else {
|
} else {
|
||||||
@@ -259,7 +282,7 @@ export default function Health() {
|
|||||||
value={systolic}
|
value={systolic}
|
||||||
onInput={(e) => setSystolic(e.detail.value)}
|
onInput={(e) => setSystolic(e.detail.value)}
|
||||||
/>
|
/>
|
||||||
<Text className='input-label' style='margin-top:20px;'>舒张压(低压)</Text>
|
<Text className='input-label input-label--secondary'>舒张压(低压)</Text>
|
||||||
<Input
|
<Input
|
||||||
className='input-field'
|
className='input-field'
|
||||||
type='number'
|
type='number'
|
||||||
@@ -344,12 +367,20 @@ export default function Health() {
|
|||||||
) : (
|
) : (
|
||||||
<View className='trend-chart'>
|
<View className='trend-chart'>
|
||||||
<View className='trend-bars'>
|
<View className='trend-bars'>
|
||||||
|
{/* 阈值标线 */}
|
||||||
|
{getThresholdValue(activeTab, thresholds) && (() => {
|
||||||
|
const tv = getThresholdValue(activeTab, thresholds)!;
|
||||||
|
const pct = Math.min(95, (tv / maxTrendValue) * 100);
|
||||||
|
return (
|
||||||
|
<View className='trend-threshold-line' style={`bottom:${((12 + pct * 1.08) / 120 * 100).toFixed(1)}%;`}>
|
||||||
|
<Text className='trend-threshold-label'>{tv}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
{trendData.map((point, i) => {
|
{trendData.map((point, i) => {
|
||||||
const heightPct = Math.max(8, (point.value / maxTrendValue) * 100);
|
const heightPct = Math.max(8, (point.value / maxTrendValue) * 100);
|
||||||
const isAbnormal = activeTab === 'blood_pressure' ? point.value > 140
|
const tv = getThresholdValue(activeTab, thresholds);
|
||||||
: activeTab === 'heart_rate' ? (point.value > 100 || point.value < 60)
|
const isAbnormal = tv ? point.value >= tv : false;
|
||||||
: activeTab === 'blood_sugar' ? point.value > 6.1
|
|
||||||
: false;
|
|
||||||
const dayOfWeek = new Date(point.date).getDay();
|
const dayOfWeek = new Date(point.date).getDay();
|
||||||
return (
|
return (
|
||||||
<View className='trend-bar-col' key={i}>
|
<View className='trend-bar-col' key={i}>
|
||||||
@@ -366,22 +397,7 @@ export default function Health() {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* BLE 设备卡片 */}
|
{/* BLE 设备同步功能暂缓开放 */}
|
||||||
<View className='device-section'>
|
|
||||||
<View
|
|
||||||
className='device-card'
|
|
||||||
onClick={() => Taro.navigateTo({ url: '/pages/device-sync/index' })}
|
|
||||||
>
|
|
||||||
<View className='device-icon'>
|
|
||||||
<Text className='device-icon-text'>设</Text>
|
|
||||||
</View>
|
|
||||||
<View className='device-info'>
|
|
||||||
<Text className='device-name'>蓝牙设备</Text>
|
|
||||||
<Text className='device-desc'>连接设备自动同步数据</Text>
|
|
||||||
</View>
|
|
||||||
<Text className='device-arrow'>›</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* 健康资讯入口 */}
|
{/* 健康资讯入口 */}
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
@import '../../styles/mixins.scss';
|
@import '../../styles/mixins.scss';
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════
|
||||||
|
登录后首页
|
||||||
|
═══════════════════════════════════════ */
|
||||||
|
|
||||||
.home-page {
|
.home-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: $bg;
|
background: $bg;
|
||||||
padding: 20px 16px 100px;
|
padding: 20px 24px 100px;
|
||||||
padding-bottom: calc(100px + env(safe-area-inset-bottom));
|
padding-bottom: calc(100px + env(safe-area-inset-bottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 区域 1:问候 + 日期 + 铃铛 ─── */
|
/* ─── 问候区 ─── */
|
||||||
.greeting-section {
|
.greeting-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.greeting-left {
|
.greeting-left {
|
||||||
@@ -21,7 +25,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.greeting-text {
|
.greeting-text {
|
||||||
font-size: 24px;
|
@include serif-number;
|
||||||
|
font-size: var(--tk-font-h1);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -29,14 +34,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.greeting-date {
|
.greeting-date {
|
||||||
font-size: 14px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.greeting-bell {
|
.greeting-bell {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 40px;
|
width: 44px;
|
||||||
height: 40px;
|
height: 44px;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: $pri-l;
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
@@ -46,16 +53,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.greeting-bell-icon {
|
.greeting-bell-icon {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body-sm);
|
||||||
|
color: $pri-d;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 区域 2:今日体征完成度 ─── */
|
.greeting-bell-dot {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
right: 6px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: $dan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── 今日体征进度 ─── */
|
||||||
.checkin-card {
|
.checkin-card {
|
||||||
background: $card;
|
background: $card;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
box-shadow: $shadow-sm;
|
box-shadow: $shadow-md;
|
||||||
padding: 16px;
|
padding: 20px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
@@ -76,7 +94,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.checkin-title {
|
.checkin-title {
|
||||||
font-size: 16px;
|
font-size: var(--tk-font-body-sm);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -90,7 +108,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.capsule {
|
.capsule {
|
||||||
font-size: 11px;
|
font-size: var(--tk-font-micro);
|
||||||
padding: 3px 8px;
|
padding: 3px 8px;
|
||||||
border-radius: $r-pill;
|
border-radius: $r-pill;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -106,9 +124,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 区域 3:今日体征 2x2 ─── */
|
/* ─── 今日体征 2x2 ─── */
|
||||||
.vitals-section {
|
.vitals-section {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
@@ -133,40 +151,39 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vital-label {
|
.vital-label {
|
||||||
font-size: 13px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vital-value-row {
|
.vital-value-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vital-value {
|
.vital-value {
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-size: 32px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
line-height: 1.1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vital-unit {
|
.vital-unit {
|
||||||
font-size: 12px;
|
font-size: var(--tk-font-micro);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
margin-left: 2px;
|
margin-left: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vital-bottom {
|
.vital-bottom {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vital-tag {
|
.vital-tag {
|
||||||
font-size: 12px;
|
font-size: var(--tk-font-micro);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
border-radius: $r-pill;
|
border-radius: $r-pill;
|
||||||
@@ -189,103 +206,85 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 区域 4:今日待办 ─── */
|
/* ─── 智能提醒卡片 ─── */
|
||||||
.todo-section {
|
.reminder-card {
|
||||||
margin-bottom: 12px;
|
background: linear-gradient(135deg, $pri 0%, $pri-d 100%);
|
||||||
}
|
|
||||||
|
|
||||||
.todo-empty {
|
|
||||||
background: $card;
|
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
padding: 24px;
|
padding: 18px;
|
||||||
text-align: center;
|
margin-bottom: 16px;
|
||||||
box-shadow: $shadow-sm;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.todo-empty-text {
|
.reminder-header {
|
||||||
font-size: 14px;
|
display: flex;
|
||||||
color: $tx2;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.todo-list {
|
.reminder-title {
|
||||||
background: $card;
|
font-size: var(--tk-font-cap);
|
||||||
border-radius: $r;
|
font-weight: 600;
|
||||||
overflow: hidden;
|
color: #fff;
|
||||||
box-shadow: $shadow-sm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.todo-item {
|
.reminder-count {
|
||||||
|
font-size: var(--tk-font-micro);
|
||||||
|
opacity: 0.7;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reminder-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
padding: 12px 16px;
|
padding: 8px 0;
|
||||||
border-bottom: 1px solid $bd;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background: $bd-l;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.todo-icon-wrap {
|
.reminder-item-border {
|
||||||
width: 36px;
|
border-top: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
height: 36px;
|
}
|
||||||
border-radius: 10px;
|
|
||||||
background: $pri-l;
|
.reminder-tag {
|
||||||
@include flex-center;
|
font-size: var(--tk-font-micro);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
font-weight: 500;
|
||||||
|
color: #fff;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.todo-icon-char {
|
.reminder-text {
|
||||||
font-size: 18px;
|
font-size: var(--tk-font-cap);
|
||||||
font-weight: bold;
|
|
||||||
color: $pri;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-info {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
color: #fff;
|
||||||
}
|
|
||||||
|
|
||||||
.todo-title {
|
|
||||||
font-size: 15px;
|
|
||||||
color: $tx;
|
|
||||||
font-weight: 500;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-sub {
|
|
||||||
font-size: 13px;
|
|
||||||
color: $tx3;
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.todo-arrow {
|
.reminder-arrow {
|
||||||
font-size: 14px;
|
opacity: 0.5;
|
||||||
color: $tx3;
|
color: #fff;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 区域 5:快捷操作 ─── */
|
/* ─── 快捷操作 ─── */
|
||||||
.action-section {
|
.action-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 16px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 52px;
|
height: 52px;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 600;
|
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -296,6 +295,7 @@
|
|||||||
.action-primary {
|
.action-primary {
|
||||||
background: $pri;
|
background: $pri;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
box-shadow: 0 2px 8px rgba(196, 98, 58, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-outline {
|
.action-outline {
|
||||||
@@ -305,6 +305,169 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-btn-text {
|
.action-btn-text {
|
||||||
font-size: 17px;
|
font-size: var(--tk-font-body-sm);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ═══════════════════════════════════════
|
||||||
|
访客首页
|
||||||
|
═══════════════════════════════════════ */
|
||||||
|
|
||||||
|
.guest-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: $bg;
|
||||||
|
padding-bottom: calc(120px + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── 轮播图 ─── */
|
||||||
|
.guest-swiper {
|
||||||
|
width: 100%;
|
||||||
|
height: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-slide {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-slide-bg {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
|
||||||
|
&--1 {
|
||||||
|
background: linear-gradient(135deg, $pri-d 0%, $pri 60%, $pri-l 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-slide:nth-child(2) .guest-slide-bg {
|
||||||
|
background: linear-gradient(135deg, $acc 0%, #3D5A40 60%, $acc-l 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-slide:nth-child(3) .guest-slide-bg {
|
||||||
|
background: linear-gradient(135deg, #8B6F4E 0%, $wrn 60%, $wrn-l 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-slide-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-slide-title {
|
||||||
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
|
font-size: var(--tk-font-h1);
|
||||||
|
font-weight: 700;
|
||||||
|
color: #FFFFFF;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-slide-desc {
|
||||||
|
font-size: var(--tk-font-body-sm);
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── 健康资讯 ─── */
|
||||||
|
.guest-section {
|
||||||
|
padding: 24px 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-section-title {
|
||||||
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
|
font-size: var(--tk-font-body);
|
||||||
|
font-weight: bold;
|
||||||
|
color: $tx;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-articles {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-article-card {
|
||||||
|
background: $card;
|
||||||
|
border-radius: $r;
|
||||||
|
padding: 16px 18px;
|
||||||
|
box-shadow: $shadow-sm;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-article-title {
|
||||||
|
font-size: var(--tk-font-body-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
color: $tx;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-article-summary {
|
||||||
|
font-size: var(--tk-font-cap);
|
||||||
|
color: var(--tk-text-secondary);
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-empty {
|
||||||
|
padding: 40px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-empty-text {
|
||||||
|
font-size: var(--tk-font-cap);
|
||||||
|
color: var(--tk-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── 底部登录引导 ─── */
|
||||||
|
.guest-login-prompt {
|
||||||
|
margin: 24px 24px 0;
|
||||||
|
background: $card;
|
||||||
|
border-radius: $r;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: $shadow-md;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-login-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: var(--tk-font-cap);
|
||||||
|
color: $tx2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-login-btn {
|
||||||
|
height: 56px;
|
||||||
|
padding: 0 28px;
|
||||||
|
background: $pri;
|
||||||
|
border-radius: $r-pill;
|
||||||
|
@include flex-center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.guest-login-btn-text {
|
||||||
|
font-size: var(--tk-font-h2);
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,54 +1,151 @@
|
|||||||
import { View, Text } from '@tarojs/components';
|
import { View, Text, Swiper, SwiperItem } from '@tarojs/components';
|
||||||
import { useState } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import Taro, { useDidShow } from '@tarojs/taro';
|
import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro';
|
||||||
import { useAuthStore } from '../../stores/auth';
|
import { useAuthStore } from '../../stores/auth';
|
||||||
|
import { useUIStore } from '../../stores/ui';
|
||||||
|
import { navigateToLogin } from '../../utils/navigate';
|
||||||
import { useHealthStore } from '../../stores/health';
|
import { useHealthStore } from '../../stores/health';
|
||||||
import ProgressRing from '../../components/ProgressRing';
|
import ProgressRing from '../../components/ProgressRing';
|
||||||
import Loading from '../../components/Loading';
|
import Loading from '../../components/Loading';
|
||||||
import { trackPageView } from '@/services/analytics';
|
import { trackPageView } from '@/services/analytics';
|
||||||
import * as appointmentApi from '@/services/appointment';
|
import * as appointmentApi from '@/services/appointment';
|
||||||
import * as followupApi from '@/services/followup';
|
import * as followupApi from '@/services/followup';
|
||||||
|
import { listPendingSuggestions, type AiSuggestionItem } from '@/services/ai-analysis';
|
||||||
|
import { notificationService } from '@/services/notification';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
interface UpcomingItem {
|
interface ReminderItem {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
text: string;
|
||||||
subtitle: string;
|
type: 'ai' | 'appointment' | 'followup';
|
||||||
type: 'appointment' | 'followup';
|
tag: string;
|
||||||
icon: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Index() {
|
// ─── 访客首页 ───
|
||||||
|
|
||||||
|
const CAROUSEL_SLIDES = [
|
||||||
|
{ id: 'slide-1', title: '专业血透中心', desc: '三甲级医护团队全程守护' },
|
||||||
|
{ id: 'slide-2', title: '智慧健康管理', desc: 'AI 驱动个性化健康方案' },
|
||||||
|
{ id: 'slide-3', title: '温馨就医环境', desc: '舒适安全的治疗体验' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function GuestHome({ modeClass }: { modeClass: string }) {
|
||||||
|
return (
|
||||||
|
<View className={`guest-page ${modeClass}`}>
|
||||||
|
{/* 轮播图 */}
|
||||||
|
<Swiper
|
||||||
|
className='guest-swiper'
|
||||||
|
indicatorDots
|
||||||
|
indicatorColor='rgba(255,255,255,0.4)'
|
||||||
|
indicatorActiveColor='#FFFFFF'
|
||||||
|
autoplay
|
||||||
|
circular
|
||||||
|
interval={4000}
|
||||||
|
duration={500}
|
||||||
|
>
|
||||||
|
{CAROUSEL_SLIDES.map((slide) => (
|
||||||
|
<SwiperItem key={slide.id}>
|
||||||
|
<View className='guest-slide'>
|
||||||
|
<View className='guest-slide-bg guest-slide-bg--1' />
|
||||||
|
<View className='guest-slide-content'>
|
||||||
|
<Text className='guest-slide-title'>{slide.title}</Text>
|
||||||
|
<Text className='guest-slide-desc'>{slide.desc}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</SwiperItem>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
|
||||||
|
{/* 功能亮点 */}
|
||||||
|
<View className='guest-section'>
|
||||||
|
<Text className='guest-section-title'>核心功能</Text>
|
||||||
|
<View className='guest-articles'>
|
||||||
|
<View className='guest-article-card'>
|
||||||
|
<Text className='guest-article-title'>健康数据管理</Text>
|
||||||
|
<Text className='guest-article-summary'>记录并追踪您的体征数据</Text>
|
||||||
|
</View>
|
||||||
|
<View className='guest-article-card'>
|
||||||
|
<Text className='guest-article-title'>智能预约排班</Text>
|
||||||
|
<Text className='guest-article-summary'>在线预约透析和治疗</Text>
|
||||||
|
</View>
|
||||||
|
<View className='guest-article-card'>
|
||||||
|
<Text className='guest-article-title'>AI 健康分析</Text>
|
||||||
|
<Text className='guest-article-summary'>个性化健康趋势解读</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 底部登录引导 */}
|
||||||
|
<View className='guest-login-prompt'>
|
||||||
|
<Text className='guest-login-text'>登录后即可使用完整健康管理服务</Text>
|
||||||
|
<View
|
||||||
|
className='guest-login-btn'
|
||||||
|
onClick={navigateToLogin}
|
||||||
|
>
|
||||||
|
<Text className='guest-login-btn-text'>立即登录</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 登录后首页 ───
|
||||||
|
|
||||||
|
function HomeDashboard({ modeClass }: { modeClass: string }) {
|
||||||
const { user, currentPatient } = useAuthStore();
|
const { user, currentPatient } = useAuthStore();
|
||||||
const { todaySummary, loading, refreshToday } = useHealthStore();
|
const { todaySummary, loading, refreshToday } = useHealthStore();
|
||||||
const [upcomingItems, setUpcomingItems] = useState<UpcomingItem[]>([]);
|
const [reminders, setReminders] = useState<ReminderItem[]>([]);
|
||||||
const [upcomingLoading, setUpcomingLoading] = useState(false);
|
const [unreadCount, setUnreadCount] = useState(0);
|
||||||
|
const [remindersLoading, setRemindersLoading] = useState(false);
|
||||||
|
|
||||||
useDidShow(() => {
|
useDidShow(() => {
|
||||||
refreshToday();
|
refreshToday();
|
||||||
loadUpcoming();
|
loadReminders();
|
||||||
|
loadUnread();
|
||||||
trackPageView('home');
|
trackPageView('home');
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadUpcoming = async () => {
|
usePullDownRefresh(() => {
|
||||||
|
Promise.all([refreshToday(true), loadReminders(), loadUnread()]).finally(() => {
|
||||||
|
Taro.stopPullDownRefresh();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadUnread = async () => {
|
||||||
|
try {
|
||||||
|
const res = await notificationService.getUnreadCount() as { data?: { count?: number } | number };
|
||||||
|
const d = res.data;
|
||||||
|
setUnreadCount(typeof d === 'object' && d ? (d.count ?? 0) : 0);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadReminders = async () => {
|
||||||
const patientId = useAuthStore.getState().currentPatient?.id;
|
const patientId = useAuthStore.getState().currentPatient?.id;
|
||||||
if (!patientId) return;
|
if (!patientId) return;
|
||||||
setUpcomingLoading(true);
|
setRemindersLoading(true);
|
||||||
try {
|
try {
|
||||||
const items: UpcomingItem[] = [];
|
const items: ReminderItem[] = [];
|
||||||
const [apptRes, taskRes] = await Promise.allSettled([
|
const [apptRes, taskRes, suggestRes] = await Promise.allSettled([
|
||||||
appointmentApi.listAppointments(patientId, 1),
|
appointmentApi.listAppointments(patientId, 1),
|
||||||
followupApi.listTasks(patientId, 'pending'),
|
followupApi.listTasks(patientId, 'pending'),
|
||||||
|
listPendingSuggestions(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (suggestRes.status === 'fulfilled') {
|
||||||
|
for (const s of suggestRes.value.data.slice(0, 1)) {
|
||||||
|
items.push({ id: s.id, text: buildSuggestionText(s), type: 'ai', tag: 'AI 建议' });
|
||||||
|
}
|
||||||
|
}
|
||||||
if (apptRes.status === 'fulfilled') {
|
if (apptRes.status === 'fulfilled') {
|
||||||
for (const a of apptRes.value.data.slice(0, 2)) {
|
for (const a of apptRes.value.data.slice(0, 1)) {
|
||||||
if (a.status === 'pending' || a.status === 'confirmed') {
|
if (a.status === 'pending' || a.status === 'confirmed') {
|
||||||
items.push({
|
items.push({
|
||||||
id: a.id,
|
id: a.id,
|
||||||
title: `${a.appointment_date} ${a.start_time}`,
|
text: `${a.appointment_date} ${a.start_time} — ${a.doctor_name || '医护'} ${a.department || '门诊'}`,
|
||||||
subtitle: `${a.doctor_name || '医护'} · ${a.department || '门诊'}`,
|
|
||||||
type: 'appointment',
|
type: 'appointment',
|
||||||
icon: '约',
|
tag: '预约',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,18 +154,17 @@ export default function Index() {
|
|||||||
for (const t of taskRes.value.data.slice(0, 1)) {
|
for (const t of taskRes.value.data.slice(0, 1)) {
|
||||||
items.push({
|
items.push({
|
||||||
id: t.id,
|
id: t.id,
|
||||||
title: t.follow_up_type,
|
text: `${t.follow_up_type} · 截止 ${t.planned_date}`,
|
||||||
subtitle: `${t.content_template?.slice(0, 20) || '随访任务'} · 截止 ${t.planned_date}`,
|
|
||||||
type: 'followup',
|
type: 'followup',
|
||||||
icon: '随',
|
tag: '随访',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setUpcomingItems(items.slice(0, 3));
|
setReminders(items.slice(0, 3));
|
||||||
} catch {
|
} catch {
|
||||||
setUpcomingItems([]);
|
setReminders([]);
|
||||||
} finally {
|
} finally {
|
||||||
setUpcomingLoading(false);
|
setRemindersLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -76,14 +172,8 @@ export default function Index() {
|
|||||||
const greeting = hour < 12 ? '上午好' : hour < 18 ? '下午好' : '晚上好';
|
const greeting = hour < 12 ? '上午好' : hour < 18 ? '下午好' : '晚上好';
|
||||||
const displayName = user?.display_name || currentPatient?.name || '访客';
|
const displayName = user?.display_name || currentPatient?.name || '访客';
|
||||||
|
|
||||||
// 计算今日体征完成度(4 个指标:血压/心率/血糖/体重)
|
|
||||||
const summary = todaySummary || {};
|
const summary = todaySummary || {};
|
||||||
const indicators = [
|
const indicators = [!!summary.blood_pressure, !!summary.heart_rate, !!summary.blood_sugar, !!summary.weight];
|
||||||
!!summary.blood_pressure,
|
|
||||||
!!summary.heart_rate,
|
|
||||||
!!summary.blood_sugar,
|
|
||||||
!!summary.weight,
|
|
||||||
];
|
|
||||||
const completedCount = indicators.filter(Boolean).length;
|
const completedCount = indicators.filter(Boolean).length;
|
||||||
const progressPercent = Math.round((completedCount / 4) * 100);
|
const progressPercent = Math.round((completedCount / 4) * 100);
|
||||||
|
|
||||||
@@ -95,9 +185,9 @@ export default function Index() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const healthItems = [
|
const healthItems = [
|
||||||
{ label: '血压', value: summary.blood_pressure ? `${summary.blood_pressure.systolic}/${summary.blood_pressure.diastolic}` : '—', unit: 'mmHg', status: summary.blood_pressure?.status, indicator: 'blood_pressure_systolic' },
|
{ label: '血压', value: summary.blood_pressure ? `${summary.blood_pressure.systolic}/${summary.blood_pressure.diastolic}` : '—', unit: 'mmHg', status: summary.blood_pressure?.status, indicator: 'systolic_bp_morning' },
|
||||||
{ label: '心率', value: summary.heart_rate ? `${summary.heart_rate.value}` : '—', unit: 'bpm', status: summary.heart_rate?.status, indicator: 'heart_rate' },
|
{ label: '心率', value: summary.heart_rate ? `${summary.heart_rate.value}` : '—', unit: 'bpm', status: summary.heart_rate?.status, indicator: 'heart_rate' },
|
||||||
{ label: '血糖', value: summary.blood_sugar ? `${summary.blood_sugar.value}` : '—', unit: 'mmol/L', status: summary.blood_sugar?.status, indicator: 'blood_sugar_fasting' },
|
{ label: '血糖', value: summary.blood_sugar ? `${summary.blood_sugar.value}` : '—', unit: 'mmol/L', status: summary.blood_sugar?.status, indicator: 'blood_sugar' },
|
||||||
{ label: '体重', value: summary.weight ? `${summary.weight.value}` : '—', unit: 'kg', status: summary.weight?.status, indicator: 'weight' },
|
{ label: '体重', value: summary.weight ? `${summary.weight.value}` : '—', unit: 'kg', status: summary.weight?.status, indicator: 'weight' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -108,8 +198,8 @@ export default function Index() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='home-page'>
|
<View className={`home-page ${modeClass}`}>
|
||||||
{/* 区域 1:问候 + 日期 + 消息入口 */}
|
{/* 问候区 */}
|
||||||
<View className='greeting-section'>
|
<View className='greeting-section'>
|
||||||
<View className='greeting-left'>
|
<View className='greeting-left'>
|
||||||
<Text className='greeting-text'>{greeting},{displayName}</Text>
|
<Text className='greeting-text'>{greeting},{displayName}</Text>
|
||||||
@@ -117,19 +207,14 @@ export default function Index() {
|
|||||||
{new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'numeric', weekday: 'short' })}
|
{new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'numeric', weekday: 'short' })}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View className='greeting-bell' onClick={() => Taro.switchTab({ url: '/pages/messages/index' })}>
|
||||||
className='greeting-bell'
|
<Text className='greeting-bell-icon'>消</Text>
|
||||||
onClick={() => Taro.switchTab({ url: '/pages/messages/index' })}
|
{unreadCount > 0 && <View className='greeting-bell-dot' />}
|
||||||
>
|
|
||||||
<Text className='greeting-bell-icon'>🔔</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 区域 2:今日体征完成度 */}
|
{/* 今日体征进度 */}
|
||||||
<View
|
<View className='checkin-card' onClick={() => Taro.switchTab({ url: '/pages/health/index' })}>
|
||||||
className='checkin-card'
|
|
||||||
onClick={() => Taro.switchTab({ url: '/pages/health/index' })}
|
|
||||||
>
|
|
||||||
<View className='checkin-left'>
|
<View className='checkin-left'>
|
||||||
<ProgressRing percent={progressPercent} />
|
<ProgressRing percent={progressPercent} />
|
||||||
</View>
|
</View>
|
||||||
@@ -139,10 +224,7 @@ export default function Index() {
|
|||||||
</Text>
|
</Text>
|
||||||
<View className='checkin-capsules'>
|
<View className='checkin-capsules'>
|
||||||
{indicatorCapsules.map((cap) => (
|
{indicatorCapsules.map((cap) => (
|
||||||
<Text
|
<Text key={cap.label} className={`capsule ${cap.done ? 'capsule-done' : 'capsule-pending'}`}>
|
||||||
key={cap.label}
|
|
||||||
className={`capsule ${cap.done ? 'capsule-done' : 'capsule-pending'}`}
|
|
||||||
>
|
|
||||||
{cap.done ? '✓ ' : ''}{cap.label}
|
{cap.done ? '✓ ' : ''}{cap.label}
|
||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
@@ -150,8 +232,9 @@ export default function Index() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 区域 3:今日体征 2x2 网格 */}
|
{/* 体征 2x2 */}
|
||||||
<View className='vitals-section'>
|
<View className='vitals-section'>
|
||||||
|
<Text className='section-title'>今日体征</Text>
|
||||||
{loading && !todaySummary ? (
|
{loading && !todaySummary ? (
|
||||||
<Loading />
|
<Loading />
|
||||||
) : (
|
) : (
|
||||||
@@ -180,58 +263,65 @@ export default function Index() {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 区域 4:今日待办(≤3 条) */}
|
{/* 智能提醒卡片 */}
|
||||||
<View className='todo-section'>
|
{!remindersLoading && reminders.length > 0 && (
|
||||||
<Text className='section-title'>今日待办</Text>
|
<View className='reminder-card'>
|
||||||
{upcomingLoading ? (
|
<View className='reminder-header'>
|
||||||
<Loading />
|
<Text className='reminder-title'>智能提醒</Text>
|
||||||
) : upcomingItems.length === 0 ? (
|
<Text className='reminder-count'>{reminders.length} 条待处理</Text>
|
||||||
<View className='todo-empty'>
|
|
||||||
<Text className='todo-empty-text'>今天没有待办事项</Text>
|
|
||||||
</View>
|
</View>
|
||||||
) : (
|
{reminders.map((r, i) => (
|
||||||
<View className='todo-list'>
|
|
||||||
{upcomingItems.map((item) => (
|
|
||||||
<View
|
<View
|
||||||
key={item.id}
|
key={r.id}
|
||||||
className='todo-item'
|
className={`reminder-item ${i > 0 ? 'reminder-item-border' : ''}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (item.type === 'appointment') {
|
if (r.type === 'appointment') Taro.navigateTo({ url: '/pages/appointment/index' });
|
||||||
Taro.navigateTo({ url: '/pages/appointment/index' });
|
else if (r.type === 'followup') Taro.navigateTo({ url: `/pages/followup/detail/index?id=${r.id}` });
|
||||||
} else {
|
|
||||||
Taro.navigateTo({ url: `/pages/followup/detail/index?id=${item.id}` });
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View className='todo-icon-wrap'>
|
<Text className='reminder-tag'>{r.tag}</Text>
|
||||||
<Text className='todo-icon-char'>{item.icon}</Text>
|
<Text className='reminder-text'>{r.text}</Text>
|
||||||
</View>
|
<Text className='reminder-arrow'>›</Text>
|
||||||
<View className='todo-info'>
|
|
||||||
<Text className='todo-title'>{item.title}</Text>
|
|
||||||
<Text className='todo-sub'>{item.subtitle}</Text>
|
|
||||||
</View>
|
|
||||||
<Text className='todo-arrow'>›</Text>
|
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* 区域 5:快捷操作 */}
|
{/* 快捷操作 */}
|
||||||
<View className='action-section'>
|
<View className='action-section'>
|
||||||
<View
|
<View className='action-btn action-primary' onClick={() => Taro.switchTab({ url: '/pages/health/index' })}>
|
||||||
className='action-btn action-primary'
|
|
||||||
onClick={() => Taro.switchTab({ url: '/pages/health/index' })}
|
|
||||||
>
|
|
||||||
<Text className='action-btn-text'>记录体征</Text>
|
<Text className='action-btn-text'>记录体征</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View className='action-btn action-outline' onClick={() => Taro.navigateTo({ url: '/pages/appointment/create/index' })}>
|
||||||
className='action-btn action-outline'
|
|
||||||
onClick={() => Taro.navigateTo({ url: '/pages/appointment/create/index' })}
|
|
||||||
>
|
|
||||||
<Text className='action-btn-text'>预约挂号</Text>
|
<Text className='action-btn-text'>预约挂号</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── 首页入口:根据登录状态切换 ───
|
||||||
|
|
||||||
|
export default function Index() {
|
||||||
|
const user = useAuthStore((s) => s.user);
|
||||||
|
const mode = useUIStore((s) => s.mode);
|
||||||
|
const modeClass = mode === 'elder' ? 'elder-mode' : '';
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return <GuestHome modeClass={modeClass} />;
|
||||||
|
}
|
||||||
|
return <HomeDashboard modeClass={modeClass} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSuggestionText(s: AiSuggestionItem): string {
|
||||||
|
const riskMap: Record<string, string> = { high: '高风险', medium: '中风险', low: '低风险' };
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
vital_sign_anomaly: '体征异常',
|
||||||
|
lab_result_anomaly: '化验异常',
|
||||||
|
medication_adherence: '用药提醒',
|
||||||
|
lifestyle: '生活建议',
|
||||||
|
};
|
||||||
|
const risk = riskMap[s.risk_level] || '';
|
||||||
|
const type = typeMap[s.suggestion_type] || '健康建议';
|
||||||
|
return `${type}:发现${risk}指标,建议关注`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,19 +8,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.legal-content {
|
.legal-content {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 34px;
|
font-size: var(--tk-font-num-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
line-height: 1.8;
|
line-height: 1.8;
|
||||||
@@ -41,6 +41,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.legal-footer-text {
|
.legal-footer-text {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 160px 56px 80px;
|
padding: 100px 40px 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 品牌区 ─── */
|
/* ─── 品牌区 ─── */
|
||||||
@@ -19,22 +19,22 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 80px;
|
margin-bottom: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-logo {
|
.login-logo {
|
||||||
width: 128px;
|
width: 96px;
|
||||||
height: 128px;
|
height: 96px;
|
||||||
border-radius: $r-lg;
|
border-radius: $r-lg;
|
||||||
background: $pri;
|
background: $pri;
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
margin-bottom: 36px;
|
margin-bottom: 24px;
|
||||||
box-shadow: 0 8px 24px rgba($pri, 0.3);
|
box-shadow: 0 8px 24px rgba($pri, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-logo-mark {
|
.login-logo-mark {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 64px;
|
font-size: var(--tk-font-hero);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -42,14 +42,14 @@
|
|||||||
|
|
||||||
.login-title {
|
.login-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 48px;
|
font-size: var(--tk-font-num);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-subtitle {
|
.login-subtitle {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-body-sm);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
/* ─── 装饰线 ─── */
|
/* ─── 装饰线 ─── */
|
||||||
.login-divider {
|
.login-divider {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
margin-bottom: 64px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-divider-line {
|
.login-divider-line {
|
||||||
@@ -74,16 +74,18 @@
|
|||||||
|
|
||||||
.login-btn {
|
.login-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 96px;
|
height: $btn-primary-h;
|
||||||
background: $pri;
|
background: $pri;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 32px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
border: none;
|
border: none;
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
box-shadow: 0 4px 16px rgba($pri, 0.25);
|
box-shadow: 0 4px 16px rgba($pri, 0.25);
|
||||||
|
padding: 0;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
border: none;
|
border: none;
|
||||||
@@ -98,14 +100,14 @@
|
|||||||
.agreement-row {
|
.agreement-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-top: 40px;
|
margin-top: 28px;
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agreement-check {
|
.agreement-check {
|
||||||
width: 32px;
|
width: 28px;
|
||||||
height: 32px;
|
height: 28px;
|
||||||
border: 2px solid $bd;
|
border: 2px solid $bd;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
@@ -120,14 +122,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.agreement-check-mark {
|
.agreement-check-mark {
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-body-sm);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agreement-text {
|
.agreement-text {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
}
|
}
|
||||||
@@ -136,3 +138,16 @@
|
|||||||
color: $pri;
|
color: $pri;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── 暂不登录 ─── */
|
||||||
|
.skip-row {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-btn {
|
||||||
|
font-size: var(--tk-font-body-sm);
|
||||||
|
color: var(--tk-text-secondary);
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,13 +2,18 @@ import { useState } from 'react';
|
|||||||
import { View, Text, Button, ScrollView } from '@tarojs/components';
|
import { View, Text, Button, ScrollView } from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import { useAuthStore } from '../../stores/auth';
|
import { useAuthStore } from '../../stores/auth';
|
||||||
|
import { useElderClass } from '../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [needBind, setNeedBind] = useState(false);
|
const [needBind, setNeedBind] = useState(false);
|
||||||
const [agreed, setAgreed] = useState(false);
|
const [agreed, setAgreed] = useState(false);
|
||||||
const { login, bindPhone, loading, isMedicalStaff } = useAuthStore();
|
const { login, bindPhone, loading, isMedicalStaff } = useAuthStore();
|
||||||
|
|
||||||
|
// 登录页不应用关怀模式(正常模式尺寸已足够大)
|
||||||
|
const loginClass = '';
|
||||||
|
|
||||||
const navigateAfterLogin = () => {
|
const navigateAfterLogin = () => {
|
||||||
if (isMedicalStaff()) {
|
if (isMedicalStaff()) {
|
||||||
Taro.redirectTo({ url: '/pages/doctor/index' });
|
Taro.redirectTo({ url: '/pages/doctor/index' });
|
||||||
@@ -47,16 +52,29 @@ export default function Login() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { encryptedData, iv } = e.detail;
|
const { encryptedData, iv } = e.detail;
|
||||||
|
try {
|
||||||
const success = await bindPhone(encryptedData, iv);
|
const success = await bindPhone(encryptedData, iv);
|
||||||
if (success) {
|
if (success) {
|
||||||
navigateAfterLogin();
|
navigateAfterLogin();
|
||||||
} else {
|
}
|
||||||
Taro.showToast({ title: '绑定失败,请重试', icon: 'none' });
|
} catch (err: any) {
|
||||||
|
const msg = err?.message || '绑定失败';
|
||||||
|
Taro.showModal({
|
||||||
|
title: '绑定手机号失败',
|
||||||
|
content: msg,
|
||||||
|
confirmText: '重新登录',
|
||||||
|
cancelText: '取消',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
setNeedBind(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollY className='login-scroll'>
|
<ScrollView scrollY className={`login-scroll ${loginClass}`}>
|
||||||
<View className='login-page'>
|
<View className='login-page'>
|
||||||
{/* 品牌区 */}
|
{/* 品牌区 */}
|
||||||
<View className='login-brand'>
|
<View className='login-brand'>
|
||||||
@@ -102,6 +120,13 @@ export default function Login() {
|
|||||||
<Text className='agreement-link' onClick={() => Taro.navigateTo({ url: '/pages/legal/privacy-policy' })}>《隐私政策》</Text>
|
<Text className='agreement-link' onClick={() => Taro.navigateTo({ url: '/pages/legal/privacy-policy' })}>《隐私政策》</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* 暂不登录 */}
|
||||||
|
<View className='skip-row'>
|
||||||
|
<Text className='skip-btn' onClick={() => Taro.reLaunch({ url: '/pages/index/index' })}>
|
||||||
|
暂不登录,先看看
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.points-label {
|
.points-label {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: rgba(255, 255, 255, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.checkin-btn-text {
|
.checkin-btn-text {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
|
|
||||||
.points-balance {
|
.points-balance {
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-size: 72px;
|
font-size: 72px; /* kept as-is: special display value */
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.points-streak {
|
.points-streak {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.type-tab-text {
|
.type-tab-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
|
|
||||||
.product-image-char {
|
.product-image-char {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 56px;
|
font-size: var(--tk-font-hero);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.product-name {
|
.product-name {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -180,20 +180,20 @@
|
|||||||
|
|
||||||
.product-points-char {
|
.product-points-char {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $wrn;
|
color: $wrn;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-points-value {
|
.product-points-value {
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $wrn;
|
color: $wrn;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-stock {
|
.product-stock {
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-body);
|
||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 160px 40px;
|
padding: 100px 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-icon {
|
.empty-icon {
|
||||||
@@ -226,22 +226,22 @@
|
|||||||
|
|
||||||
.empty-char {
|
.empty-char {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 52px;
|
font-size: var(--tk-font-hero);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-title {
|
.empty-title {
|
||||||
font-size: 32px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-hint {
|
.empty-hint {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
@@ -257,7 +257,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.empty-action-text {
|
.empty-action-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { PointsProduct } from '../../services/points';
|
|||||||
import { useAuthStore } from '../../stores/auth';
|
import { useAuthStore } from '../../stores/auth';
|
||||||
import { usePointsStore } from '../../stores/points';
|
import { usePointsStore } from '../../stores/points';
|
||||||
import Loading from '../../components/Loading';
|
import Loading from '../../components/Loading';
|
||||||
|
import { useElderClass } from '../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const PRODUCT_TYPE_TABS = [
|
const PRODUCT_TYPE_TABS = [
|
||||||
@@ -32,6 +33,7 @@ export default function Mall() {
|
|||||||
const [checkinLoading, setCheckinLoading] = useState(false);
|
const [checkinLoading, setCheckinLoading] = useState(false);
|
||||||
const [noProfile, setNoProfile] = useState(false);
|
const [noProfile, setNoProfile] = useState(false);
|
||||||
const loadingRef = useRef(false);
|
const loadingRef = useRef(false);
|
||||||
|
const modeClass = useElderClass();
|
||||||
|
|
||||||
const fetchProducts = useCallback(
|
const fetchProducts = useCallback(
|
||||||
async (pageNum: number, type: string, isRefresh = false) => {
|
async (pageNum: number, type: string, isRefresh = false) => {
|
||||||
@@ -132,7 +134,7 @@ export default function Mall() {
|
|||||||
|
|
||||||
if (noProfile) {
|
if (noProfile) {
|
||||||
return (
|
return (
|
||||||
<View className='mall-page'>
|
<View className={`mall-page ${modeClass}`}>
|
||||||
<View className='mall-empty-state'>
|
<View className='mall-empty-state'>
|
||||||
<View className='empty-icon'>
|
<View className='empty-icon'>
|
||||||
<Text className='empty-char'>档</Text>
|
<Text className='empty-char'>档</Text>
|
||||||
@@ -148,7 +150,7 @@ export default function Mall() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='mall-page'>
|
<View className={`mall-page ${modeClass}`}>
|
||||||
{/* 积分余额卡片 */}
|
{/* 积分余额卡片 */}
|
||||||
<View className='mall-header'>
|
<View className='mall-header'>
|
||||||
<View className='points-card'>
|
<View className='points-card'>
|
||||||
|
|||||||
@@ -4,70 +4,86 @@
|
|||||||
.messages-page {
|
.messages-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: $bg;
|
background: $bg;
|
||||||
padding-bottom: calc(120px + env(safe-area-inset-bottom));
|
padding: 20px 24px 100px;
|
||||||
|
padding-bottom: calc(100px + env(safe-area-inset-bottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 页头 ─── */
|
/* ─── 页头 ─── */
|
||||||
.messages-header {
|
.messages-header {
|
||||||
padding: 24px 32px 8px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages-title {
|
.messages-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
@include serif-number;
|
||||||
font-size: 36px;
|
font-size: var(--tk-font-h1);
|
||||||
font-weight: bold;
|
font-weight: 700;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Tab 切换 ─── */
|
/* ─── 分段控件 Tab ─── */
|
||||||
.msg-tabs {
|
.msg-segment {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 16px 24px 0;
|
|
||||||
gap: 0;
|
gap: 0;
|
||||||
|
background: $surface-alt;
|
||||||
|
border-radius: $r-sm;
|
||||||
|
padding: 3px;
|
||||||
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-tab {
|
.msg-segment-tab {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: $tab-h;
|
height: 40px;
|
||||||
|
border-radius: $r-xs;
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-tab-text {
|
.msg-segment-active {
|
||||||
font-size: 28px;
|
background: $card;
|
||||||
|
box-shadow: $shadow-sm;
|
||||||
|
|
||||||
|
.msg-segment-text {
|
||||||
|
color: $tx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-segment-text {
|
||||||
|
font-size: var(--tk-font-cap);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx2;
|
color: $tx3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-tab-active .msg-tab-text {
|
.msg-segment-badge {
|
||||||
color: $pri;
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 12px;
|
||||||
|
min-width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: $dan;
|
||||||
|
@include flex-center;
|
||||||
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-tab-indicator {
|
.msg-segment-badge-text {
|
||||||
padding: 0 24px;
|
font-size: var(--tk-font-micro);
|
||||||
height: 3px;
|
color: #fff;
|
||||||
background: $bd-l;
|
font-weight: 600;
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.msg-tab-bar {
|
|
||||||
width: 50%;
|
|
||||||
height: 3px;
|
|
||||||
background: $pri;
|
|
||||||
border-radius: 2px;
|
|
||||||
transition: transform 0.2s;
|
|
||||||
|
|
||||||
&.msg-tab-bar-right {
|
|
||||||
transform: translateX(100%);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 内容区 ─── */
|
/* ─── 内容区 ─── */
|
||||||
.msg-content {
|
.msg-content {
|
||||||
padding: 0 24px;
|
// wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-empty {
|
.msg-empty {
|
||||||
@@ -79,18 +95,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.msg-empty-text {
|
.msg-empty-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── 咨询卡片 ─── */
|
/* ─── 咨询卡片 ─── */
|
||||||
.consult-card {
|
.consult-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: $card;
|
background: $card;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
padding: 24px;
|
padding: 16px;
|
||||||
margin-bottom: 12px;
|
|
||||||
box-shadow: $shadow-sm;
|
box-shadow: $shadow-sm;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -98,53 +114,84 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.consult-info {
|
.consult-card-muted {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consult-avatar {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: $surface-alt;
|
||||||
|
@include flex-center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consult-avatar-active {
|
||||||
|
background: $pri-l;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consult-avatar-char {
|
||||||
|
@include serif-number;
|
||||||
|
font-size: var(--tk-font-body-sm);
|
||||||
|
font-weight: 700;
|
||||||
|
color: $tx3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consult-avatar-active .consult-avatar-char {
|
||||||
|
color: $pri;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consult-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.consult-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.consult-doctor {
|
.consult-doctor {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-cap);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.consult-preview {
|
|
||||||
font-size: 24px;
|
|
||||||
color: $tx2;
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.consult-meta {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.consult-time {
|
.consult-time {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-micro);
|
||||||
|
color: var(--tk-text-secondary);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consult-preview {
|
||||||
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.consult-badge {
|
.consult-badge {
|
||||||
min-width: 24px;
|
min-width: 18px;
|
||||||
height: 24px;
|
height: 18px;
|
||||||
border-radius: 12px;
|
border-radius: 9px;
|
||||||
background: $dan;
|
background: $dan;
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
padding: 0 6px;
|
padding: 0 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.consult-badge-text {
|
.consult-badge-text {
|
||||||
font-size: 18px;
|
font-size: var(--tk-font-micro);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -152,43 +199,72 @@
|
|||||||
/* ─── 通知卡片 ─── */
|
/* ─── 通知卡片 ─── */
|
||||||
.notify-card {
|
.notify-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
background: $card;
|
background: $card;
|
||||||
border-radius: $r;
|
border-radius: $r;
|
||||||
padding: 24px;
|
padding: 16px;
|
||||||
margin-bottom: 12px;
|
|
||||||
box-shadow: $shadow-sm;
|
box-shadow: $shadow-sm;
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-info {
|
.notify-card-muted {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notify-icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: $r-sm;
|
||||||
|
@include flex-center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notify-icon-char {
|
||||||
|
@include serif-number;
|
||||||
|
font-size: var(--tk-font-body-sm);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notify-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-title {
|
.notify-row {
|
||||||
font-size: 28px;
|
display: flex;
|
||||||
font-weight: 600;
|
justify-content: space-between;
|
||||||
color: $tx;
|
align-items: center;
|
||||||
display: block;
|
margin-bottom: 4px;
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-desc {
|
.notify-title {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-cap);
|
||||||
color: $tx2;
|
font-weight: 400;
|
||||||
display: block;
|
color: $tx;
|
||||||
overflow: hidden;
|
}
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
.notify-title-bold {
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-time {
|
.notify-time {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-micro);
|
||||||
color: $tx2;
|
color: var(--tk-text-secondary);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-left: 16px;
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notify-desc {
|
||||||
|
font-size: var(--tk-font-cap);
|
||||||
|
color: $tx2;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notify-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: $pri;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useRef } from 'react';
|
||||||
import { View, Text } from '@tarojs/components';
|
import { View, Text } from '@tarojs/components';
|
||||||
import Taro, { useDidShow } from '@tarojs/taro';
|
import Taro, { useDidShow, useReachBottom } from '@tarojs/taro';
|
||||||
import { listConsultations, ConsultationSession } from '../../services/consultation';
|
import { listConsultations, ConsultationSession } from '../../services/consultation';
|
||||||
import { notificationService } from '../../services/notification';
|
import { notificationService } from '../../services/notification';
|
||||||
import Loading from '../../components/Loading';
|
import Loading from '../../components/Loading';
|
||||||
|
import GuestGuard from '../../components/GuestGuard';
|
||||||
|
import { useAuthStore } from '../../stores/auth';
|
||||||
|
import { useElderClass } from '../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
type MsgTab = 'consultation' | 'notification';
|
type MsgTab = 'consultation' | 'notification';
|
||||||
@@ -14,41 +17,80 @@ interface NotificationItem {
|
|||||||
desc: string;
|
desc: string;
|
||||||
time: string;
|
time: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
read?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NOTIFY_ICONS: Record<string, { icon: string; bg: string; color: string }> = {
|
||||||
|
appointment: { icon: '约', bg: '#F0DDD4', color: '#C4623A' },
|
||||||
|
alert: { icon: '警', bg: '#FFF3E0', color: '#C4873A' },
|
||||||
|
followup: { icon: '随', bg: '#E8F0E8', color: '#5B7A5E' },
|
||||||
|
points: { icon: '分', bg: '#F0DDD4', color: '#C4623A' },
|
||||||
|
report: { icon: '报', bg: '#E8F0E8', color: '#5B7A5E' },
|
||||||
|
};
|
||||||
|
|
||||||
export default function Messages() {
|
export default function Messages() {
|
||||||
|
const user = useAuthStore((s) => s.user);
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [activeTab, setActiveTab] = useState<MsgTab>('consultation');
|
const [activeTab, setActiveTab] = useState<MsgTab>('consultation');
|
||||||
const [sessions, setSessions] = useState<ConsultationSession[]>([]);
|
const [sessions, setSessions] = useState<ConsultationSession[]>([]);
|
||||||
const [notifications, setNotifications] = useState<NotificationItem[]>([]);
|
const [notifications, setNotifications] = useState<NotificationItem[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const loadingRef = useRef(false);
|
||||||
|
|
||||||
useDidShow(() => {
|
const loadData = async (tab: MsgTab, pageNum: number = 1, isRefresh = false) => {
|
||||||
loadData(activeTab);
|
if (loadingRef.current) return;
|
||||||
});
|
loadingRef.current = true;
|
||||||
|
|
||||||
const loadData = async (tab: MsgTab) => {
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
if (tab === 'consultation') {
|
if (tab === 'consultation') {
|
||||||
const res = await listConsultations({ page: 1, page_size: 20 });
|
const res = await listConsultations({ page: pageNum, page_size: 20 });
|
||||||
setSessions(res.data || []);
|
const list = res.data || [];
|
||||||
|
if (isRefresh) {
|
||||||
|
setSessions(list);
|
||||||
} else {
|
} else {
|
||||||
const res = await notificationService.list<{ data: unknown[] }>({ page: 1, page_size: 20 });
|
setSessions((prev) => [...prev, ...list]);
|
||||||
setNotifications((res as { data?: unknown[] })?.data || []);
|
|
||||||
}
|
}
|
||||||
|
setTotal(res.total || 0);
|
||||||
|
} else {
|
||||||
|
const res = await notificationService.list<{ data: unknown[]; total?: number }>({ page: pageNum, page_size: 20 });
|
||||||
|
const list = (res as { data?: unknown[] })?.data || [];
|
||||||
|
if (isRefresh) {
|
||||||
|
setNotifications(list as NotificationItem[]);
|
||||||
|
} else {
|
||||||
|
setNotifications((prev) => [...prev, ...(list as NotificationItem[])]);
|
||||||
|
}
|
||||||
|
setTotal((res as { total?: number })?.total || 0);
|
||||||
|
}
|
||||||
|
setPage(pageNum);
|
||||||
} catch {
|
} catch {
|
||||||
|
if (isRefresh) {
|
||||||
if (tab === 'consultation') setSessions([]);
|
if (tab === 'consultation') setSessions([]);
|
||||||
else setNotifications([]);
|
else setNotifications([]);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
loadingRef.current = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useDidShow(() => {
|
||||||
|
if (user) loadData(activeTab, 1, true);
|
||||||
|
});
|
||||||
|
|
||||||
const handleTabChange = (tab: MsgTab) => {
|
const handleTabChange = (tab: MsgTab) => {
|
||||||
setActiveTab(tab);
|
setActiveTab(tab);
|
||||||
loadData(tab);
|
loadData(tab, 1, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useReachBottom(() => {
|
||||||
|
const currentList = activeTab === 'consultation' ? sessions : notifications;
|
||||||
|
if (!loading && currentList.length < total) {
|
||||||
|
loadData(activeTab, page + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const formatTime = (dateStr: string | null) => {
|
const formatTime = (dateStr: string | null) => {
|
||||||
if (!dateStr) return '';
|
if (!dateStr) return '';
|
||||||
const d = new Date(dateStr);
|
const d = new Date(dateStr);
|
||||||
@@ -61,59 +103,75 @@ export default function Messages() {
|
|||||||
return dateStr.slice(0, 10);
|
return dateStr.slice(0, 10);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return <GuestGuard title='请先登录' desc='登录后即可查看消息和通知' />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unreadConsultCount = sessions.filter((s) => s.unread_count_patient > 0).length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='messages-page'>
|
<View className={`messages-page ${modeClass}`}>
|
||||||
{/* 页头 */}
|
{/* 页头 */}
|
||||||
<View className='messages-header'>
|
<View className='messages-header'>
|
||||||
<Text className='messages-title'>消息</Text>
|
<Text className='messages-title'>消息</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Tab 切换 */}
|
{/* 分段控件 Tab */}
|
||||||
<View className='msg-tabs'>
|
<View className='msg-segment'>
|
||||||
<View
|
<View
|
||||||
className={`msg-tab ${activeTab === 'consultation' ? 'msg-tab-active' : ''}`}
|
className={`msg-segment-tab ${activeTab === 'consultation' ? 'msg-segment-active' : ''}`}
|
||||||
onClick={() => handleTabChange('consultation')}
|
onClick={() => handleTabChange('consultation')}
|
||||||
>
|
>
|
||||||
<Text className='msg-tab-text'>咨询</Text>
|
<Text className='msg-segment-text'>咨询</Text>
|
||||||
|
{unreadConsultCount > 0 && (
|
||||||
|
<View className='msg-segment-badge'>
|
||||||
|
<Text className='msg-segment-badge-text'>{unreadConsultCount}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
className={`msg-tab ${activeTab === 'notification' ? 'msg-tab-active' : ''}`}
|
className={`msg-segment-tab ${activeTab === 'notification' ? 'msg-segment-active' : ''}`}
|
||||||
onClick={() => handleTabChange('notification')}
|
onClick={() => handleTabChange('notification')}
|
||||||
>
|
>
|
||||||
<Text className='msg-tab-text'>通知</Text>
|
<Text className='msg-segment-text'>通知</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View className='msg-tab-indicator'>
|
|
||||||
<View className={`msg-tab-bar ${activeTab === 'notification' ? 'msg-tab-bar-right' : ''}`} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
|
<View className='msg-content'>
|
||||||
{/* 咨询列表 */}
|
{/* 咨询列表 */}
|
||||||
{activeTab === 'consultation' && (
|
{activeTab === 'consultation' && (
|
||||||
<View className='msg-content'>
|
loading ? (
|
||||||
{loading ? (
|
|
||||||
<Loading />
|
<Loading />
|
||||||
) : sessions.length === 0 ? (
|
) : sessions.length === 0 ? (
|
||||||
<View className='msg-empty'>
|
<View className='msg-empty'>
|
||||||
<Text className='msg-empty-text'>暂无咨询消息</Text>
|
<Text className='msg-empty-text'>暂无咨询消息</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
sessions.map((session) => (
|
<View className='msg-list'>
|
||||||
|
{sessions.map((session) => {
|
||||||
|
const doctorName = session.last_message?.slice(0, 1) || '医';
|
||||||
|
const hasUnread = session.unread_count_patient > 0;
|
||||||
|
return (
|
||||||
<View
|
<View
|
||||||
key={session.id}
|
key={session.id}
|
||||||
className='consult-card'
|
className={`consult-card ${hasUnread ? '' : 'consult-card-muted'}`}
|
||||||
onClick={() => Taro.navigateTo({ url: `/pages/consultation/detail/index?id=${session.id}` })}
|
onClick={() => Taro.navigateTo({ url: `/pages/consultation/detail/index?id=${session.id}` })}
|
||||||
>
|
>
|
||||||
<View className='consult-info'>
|
<View className={`consult-avatar ${hasUnread ? 'consult-avatar-active' : ''}`}>
|
||||||
|
<Text className='consult-avatar-char'>{doctorName}</Text>
|
||||||
|
</View>
|
||||||
|
<View className='consult-body'>
|
||||||
|
<View className='consult-row'>
|
||||||
<Text className='consult-doctor'>
|
<Text className='consult-doctor'>
|
||||||
{session.consultation_type === 'online' ? '在线咨询' : '门诊咨询'}
|
{session.consultation_type === 'online' ? '在线咨询' : '门诊咨询'}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text className='consult-time'>{formatTime(session.last_message_at)}</Text>
|
||||||
|
</View>
|
||||||
|
<View className='consult-row'>
|
||||||
<Text className='consult-preview'>
|
<Text className='consult-preview'>
|
||||||
{session.last_message || session.subject || '暂无消息'}
|
{session.last_message || session.subject || '暂无消息'}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
{hasUnread && (
|
||||||
<View className='consult-meta'>
|
|
||||||
<Text className='consult-time'>{formatTime(session.last_message_at)}</Text>
|
|
||||||
{session.unread_count_patient > 0 && (
|
|
||||||
<View className='consult-badge'>
|
<View className='consult-badge'>
|
||||||
<Text className='consult-badge-text'>
|
<Text className='consult-badge-text'>
|
||||||
{session.unread_count_patient > 99 ? '99+' : session.unread_count_patient}
|
{session.unread_count_patient > 99 ? '99+' : session.unread_count_patient}
|
||||||
@@ -122,33 +180,46 @@ export default function Messages() {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
))
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 通知列表 */}
|
{/* 通知列表 */}
|
||||||
{activeTab === 'notification' && (
|
{activeTab === 'notification' && (
|
||||||
<View className='msg-content'>
|
loading ? (
|
||||||
{loading ? (
|
|
||||||
<Loading />
|
<Loading />
|
||||||
) : notifications.length === 0 ? (
|
) : notifications.length === 0 ? (
|
||||||
<View className='msg-empty'>
|
<View className='msg-empty'>
|
||||||
<Text className='msg-empty-text'>暂无新通知</Text>
|
<Text className='msg-empty-text'>暂无新通知</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
notifications.map((n) => (
|
<View className='msg-list'>
|
||||||
<View key={n.id} className='notify-card'>
|
{notifications.map((n) => {
|
||||||
<View className='notify-info'>
|
const cfg = NOTIFY_ICONS[n.type] || NOTIFY_ICONS.report;
|
||||||
<Text className='notify-title'>{n.title}</Text>
|
const isUnread = !n.read;
|
||||||
<Text className='notify-desc'>{n.desc}</Text>
|
return (
|
||||||
|
<View key={n.id} className={`notify-card ${isUnread ? '' : 'notify-card-muted'}`}>
|
||||||
|
<View className='notify-icon' style={`background:${cfg.bg};`}>
|
||||||
|
<Text className='notify-icon-char' style={`color:${cfg.color};`}>{cfg.icon}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
<View className='notify-body'>
|
||||||
|
<View className='notify-row'>
|
||||||
|
<Text className={`notify-title ${isUnread ? 'notify-title-bold' : ''}`}>{n.title}</Text>
|
||||||
<Text className='notify-time'>{n.time}</Text>
|
<Text className='notify-time'>{n.time}</Text>
|
||||||
</View>
|
</View>
|
||||||
))
|
<Text className='notify-desc'>{n.desc}</Text>
|
||||||
|
</View>
|
||||||
|
{isUnread && <View className='notify-dot' />}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,37 @@
|
|||||||
|
@import '../../../styles/variables.scss';
|
||||||
|
@import '../../../styles/mixins.scss';
|
||||||
|
|
||||||
.alerts-page {
|
.alerts-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #f5f5f5;
|
background: $bg;
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
.alerts-tabs {
|
.alerts-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
background: #fff;
|
background: $card;
|
||||||
padding: 20px 16px;
|
padding: 20px 16px;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid $bd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alerts-tab {
|
.alerts-tab {
|
||||||
padding: 8px 20px;
|
padding: 8px 20px;
|
||||||
border-radius: 20px;
|
border-radius: $r-pill;
|
||||||
background: #f0f0f0;
|
background: $surface-alt;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alerts-tab.active {
|
.alerts-tab.active {
|
||||||
background: #C4623A;
|
background: $pri;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alerts-tab-text {
|
.alerts-tab-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: #666;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alerts-tab-text.active {
|
.alerts-tab-text.active {
|
||||||
color: #fff;
|
color: $card;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alerts-list {
|
.alerts-list {
|
||||||
@@ -36,10 +39,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.alert-card {
|
.alert-card {
|
||||||
background: #fff;
|
background: $card;
|
||||||
border-radius: 16px;
|
border-radius: $r;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
box-shadow: $shadow-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-header {
|
.alert-header {
|
||||||
@@ -51,54 +55,54 @@
|
|||||||
|
|
||||||
.alert-badge {
|
.alert-badge {
|
||||||
padding: 4px 16px;
|
padding: 4px 16px;
|
||||||
border-radius: 8px;
|
border-radius: $r-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-badge.sev-info {
|
.alert-badge.sev-info {
|
||||||
background: #e6f7ff;
|
background: $bd-l;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-badge.sev-warning {
|
.alert-badge.sev-warning {
|
||||||
background: #fff7e6;
|
background: $wrn-l;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-badge.sev-critical {
|
.alert-badge.sev-critical {
|
||||||
background: #fff1f0;
|
background: $dan-l;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-badge.sev-urgent {
|
.alert-badge.sev-urgent {
|
||||||
background: #ff4d4f;
|
background: $dan;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-badge-text {
|
.alert-badge-text {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-badge.sev-info .alert-badge-text {
|
.alert-badge.sev-info .alert-badge-text {
|
||||||
color: #1890ff;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-badge.sev-warning .alert-badge-text {
|
.alert-badge.sev-warning .alert-badge-text {
|
||||||
color: #fa8c16;
|
color: $wrn;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-badge.sev-critical .alert-badge-text {
|
.alert-badge.sev-critical .alert-badge-text {
|
||||||
color: #f5222d;
|
color: $dan;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-badge.sev-urgent .alert-badge-text {
|
.alert-badge.sev-urgent .alert-badge-text {
|
||||||
color: #fff;
|
color: $card;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-time {
|
.alert-time {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: #999;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-title {
|
.alert-title {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: #333;
|
color: $tx;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,24 +115,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.alerts-empty-text {
|
.alerts-empty-text {
|
||||||
font-size: 30px;
|
font-size: var(--tk-font-num);
|
||||||
color: #999;
|
color: var(--tk-text-secondary);
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alerts-empty-hint {
|
.alerts-empty-hint {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: #bbb;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.alerts-empty-action {
|
.alerts-empty-action {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
padding: 16px 48px;
|
padding: 16px 48px;
|
||||||
background: #C4623A;
|
background: $pri;
|
||||||
border-radius: 32px;
|
border-radius: $r-pill;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alerts-empty-action-text {
|
.alerts-empty-action-text {
|
||||||
color: #fff;
|
color: $card;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro';
|
|||||||
import { listPatientAlerts, type Alert } from '@/services/alert';
|
import { listPatientAlerts, type Alert } from '@/services/alert';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const SEVERITY_MAP: Record<string, { label: string; className: string }> = {
|
const SEVERITY_MAP: Record<string, { label: string; className: string }> = {
|
||||||
@@ -21,6 +22,7 @@ const STATUS_TABS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function PatientAlerts() {
|
export default function PatientAlerts() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const { currentPatient } = useAuthStore();
|
const { currentPatient } = useAuthStore();
|
||||||
const [alerts, setAlerts] = useState<Alert[]>([]);
|
const [alerts, setAlerts] = useState<Alert[]>([]);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
@@ -74,7 +76,7 @@ export default function PatientAlerts() {
|
|||||||
|
|
||||||
if (!currentPatient) {
|
if (!currentPatient) {
|
||||||
return (
|
return (
|
||||||
<View className='alerts-page'>
|
<View className={`alerts-page ${modeClass}`}>
|
||||||
<View className='alerts-empty'>
|
<View className='alerts-empty'>
|
||||||
<Text className='alerts-empty-text'>请先完善个人档案</Text>
|
<Text className='alerts-empty-text'>请先完善个人档案</Text>
|
||||||
<View className='alerts-empty-action' onClick={() => Taro.navigateTo({ url: '/pages/pkg-profile/family-add/index' })}>
|
<View className='alerts-empty-action' onClick={() => Taro.navigateTo({ url: '/pages/pkg-profile/family-add/index' })}>
|
||||||
@@ -86,7 +88,7 @@ export default function PatientAlerts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='alerts-page'>
|
<View className={`alerts-page ${modeClass}`}>
|
||||||
<View className='alerts-tabs'>
|
<View className='alerts-tabs'>
|
||||||
{STATUS_TABS.map((tab) => (
|
{STATUS_TABS.map((tab) => (
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -26,20 +26,20 @@
|
|||||||
|
|
||||||
.dm-hero-icon-text {
|
.dm-hero-icon-text {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 40px;
|
font-size: var(--tk-font-hero);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dm-hero-title {
|
.dm-hero-title {
|
||||||
@include section-title;
|
@include section-title;
|
||||||
font-size: 36px;
|
font-size: var(--tk-font-num-lg);
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dm-hero-sub {
|
.dm-hero-sub {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── card (standalone, used for date picker) ── */
|
/* ── card (standalone, used for date picker) ── */
|
||||||
@@ -60,14 +60,14 @@
|
|||||||
|
|
||||||
.dm-card-title {
|
.dm-card-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dm-card-badge {
|
.dm-card-badge {
|
||||||
@include tag($acc-l, $acc);
|
@include tag($acc-l, $acc);
|
||||||
font-size: 20px;
|
font-size: var(--tk-font-body);
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dm-date-value {
|
.dm-date-value {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -90,8 +90,8 @@
|
|||||||
|
|
||||||
.dm-date-arrow {
|
.dm-date-arrow {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@@ -118,14 +118,14 @@
|
|||||||
|
|
||||||
.dm-group-title {
|
.dm-group-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dm-group-arrow {
|
.dm-group-arrow {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
transition: transform 0.2s ease;
|
transition: transform 0.2s ease;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dm-field-label {
|
.dm-field-label {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -185,14 +185,14 @@
|
|||||||
|
|
||||||
.dm-bp-slash {
|
.dm-bp-slash {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 36px;
|
font-size: var(--tk-font-num-lg);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dm-field-unit {
|
.dm-field-unit {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@@ -210,8 +210,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dm-unit-inline {
|
.dm-unit-inline {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -221,7 +221,7 @@
|
|||||||
background: $bg;
|
background: $bg;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
padding: 20px 24px;
|
padding: 20px 24px;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -238,7 +238,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dm-field-warning {
|
.dm-field-warning {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $wrn;
|
color: $wrn;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -269,7 +269,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dm-submit-text {
|
.dm-submit-text {
|
||||||
font-size: 32px;
|
font-size: var(--tk-font-num);
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
@@ -283,6 +283,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dm-reset-text {
|
.dm-reset-text {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useHealthStore } from '@/stores/health';
|
|||||||
import { usePointsStore } from '@/stores/points';
|
import { usePointsStore } from '@/stores/points';
|
||||||
import { clearRequestCache } from '@/services/request';
|
import { clearRequestCache } from '@/services/request';
|
||||||
import { trackEvent } from '@/services/analytics';
|
import { trackEvent } from '@/services/analytics';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const bpSchema = z.number().min(30, '血压值不能低于30').max(300, '血压值不能高于300').optional();
|
const bpSchema = z.number().min(30, '血压值不能低于30').max(300, '血压值不能高于300').optional();
|
||||||
@@ -58,6 +59,7 @@ const FIELD_LABELS: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function DailyMonitoring() {
|
export default function DailyMonitoring() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const { currentPatient } = useAuthStore();
|
const { currentPatient } = useAuthStore();
|
||||||
|
|
||||||
const today = formatDate(new Date());
|
const today = formatDate(new Date());
|
||||||
@@ -258,7 +260,7 @@ export default function DailyMonitoring() {
|
|||||||
const bloodSugarAbnormal = checkAbnormal(bloodSugar, 'bloodSugar');
|
const bloodSugarAbnormal = checkAbnormal(bloodSugar, 'bloodSugar');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='dm-page'>
|
<View className={`dm-page ${modeClass}`}>
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
<View className='dm-hero'>
|
<View className='dm-hero'>
|
||||||
<View className='dm-hero-icon'>
|
<View className='dm-hero-icon'>
|
||||||
|
|||||||
@@ -26,20 +26,20 @@
|
|||||||
|
|
||||||
.input-hero-icon-text {
|
.input-hero-icon-text {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 40px;
|
font-size: var(--tk-font-hero);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-hero-title {
|
.input-hero-title {
|
||||||
@include section-title;
|
@include section-title;
|
||||||
font-size: 36px;
|
font-size: var(--tk-font-num-lg);
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-hero-sub {
|
.input-hero-sub {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── sync entry ── */
|
/* ── sync entry ── */
|
||||||
@@ -60,14 +60,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-sync-entry-text {
|
.input-sync-entry-text {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-sync-entry-hint {
|
.input-sync-entry-hint {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── card ── */
|
/* ── card ── */
|
||||||
@@ -96,14 +96,14 @@
|
|||||||
|
|
||||||
.input-card-indicator-char {
|
.input-card-indicator-char {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $acc;
|
color: $acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-card-label {
|
.input-card-label {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
}
|
}
|
||||||
@@ -119,15 +119,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-picker-value {
|
.input-picker-value {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-picker-arrow {
|
.input-picker-arrow {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
/* ── section title ── */
|
/* ── section title ── */
|
||||||
.input-section-title {
|
.input-section-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-field-label {
|
.input-field-label {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -176,8 +176,8 @@
|
|||||||
|
|
||||||
.input-bp-slash {
|
.input-bp-slash {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 36px;
|
font-size: var(--tk-font-num-lg);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@
|
|||||||
background: $bg;
|
background: $bg;
|
||||||
border-radius: $r-sm;
|
border-radius: $r-sm;
|
||||||
padding: 20px 24px;
|
padding: 20px 24px;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $tx;
|
color: $tx;
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -197,8 +197,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-field-unit {
|
.input-field-unit {
|
||||||
font-size: 22px;
|
font-size: var(--tk-font-body);
|
||||||
color: $tx3;
|
color: var(--tk-text-secondary);
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@@ -225,7 +225,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input-submit-text {
|
.input-submit-text {
|
||||||
font-size: 32px;
|
font-size: var(--tk-font-num);
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useHealthStore } from '@/stores/health';
|
|||||||
import { usePointsStore } from '@/stores/points';
|
import { usePointsStore } from '@/stores/points';
|
||||||
import { clearRequestCache } from '@/services/request';
|
import { clearRequestCache } from '@/services/request';
|
||||||
import { trackEvent } from '@/services/analytics';
|
import { trackEvent } from '@/services/analytics';
|
||||||
|
import { useElderClass } from '../../../hooks/useElderClass';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const INDICATORS = [
|
const INDICATORS = [
|
||||||
@@ -56,6 +57,7 @@ function getWarnForIndicator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function HealthInput() {
|
export default function HealthInput() {
|
||||||
|
const modeClass = useElderClass();
|
||||||
const [indicatorIdx, setIndicatorIdx] = useState(0);
|
const [indicatorIdx, setIndicatorIdx] = useState(0);
|
||||||
const [thresholds, setThresholds] = useState<HealthThreshold[]>(DEFAULT_THRESHOLDS);
|
const [thresholds, setThresholds] = useState<HealthThreshold[]>(DEFAULT_THRESHOLDS);
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
@@ -155,7 +157,7 @@ export default function HealthInput() {
|
|||||||
const indicatorInitial = INDICATORS[indicatorIdx].label.charAt(0);
|
const indicatorInitial = INDICATORS[indicatorIdx].label.charAt(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='input-page'>
|
<View className={`input-page ${modeClass}`}>
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
<View className='input-hero'>
|
<View className='input-hero'>
|
||||||
<View className='input-hero-icon'>
|
<View className='input-hero-icon'>
|
||||||
|
|||||||
@@ -26,14 +26,14 @@
|
|||||||
|
|
||||||
.trend-hero-icon-text {
|
.trend-hero-icon-text {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 40px;
|
font-size: var(--tk-font-hero);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $pri;
|
color: $pri;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trend-hero-title {
|
.trend-hero-title {
|
||||||
@include section-title;
|
@include section-title;
|
||||||
font-size: 36px;
|
font-size: var(--tk-font-num-lg);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.trange-tab-text {
|
.trange-tab-text {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -91,13 +91,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.trend-ref-label {
|
.trend-ref-label {
|
||||||
font-size: 24px;
|
font-size: var(--tk-font-h2);
|
||||||
color: $acc;
|
color: $acc;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trend-ref-value {
|
.trend-ref-value {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $acc;
|
color: $acc;
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
|
|
||||||
.trend-list-title {
|
.trend-list-title {
|
||||||
font-family: 'Georgia', 'Times New Roman', serif;
|
font-family: 'Georgia', 'Times New Roman', serif;
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $tx;
|
color: $tx;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.trend-item-date {
|
.trend-item-date {
|
||||||
font-size: 26px;
|
font-size: var(--tk-font-h1);
|
||||||
color: $tx2;
|
color: $tx2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.trend-item-value {
|
.trend-item-value {
|
||||||
font-size: 28px;
|
font-size: var(--tk-font-body-lg);
|
||||||
color: $pri;
|
color: $pri;
|
||||||
@include serif-number;
|
@include serif-number;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user