feat(mp+health): 小程序分包迁移 + 积分商城后台列表 API
- 小程序页面迁移到 pkg-health/pkg-mall/pkg-profile 分包目录 - 删除旧 pages/health/input、pages/mall/detail 等旧路径 - 导航路径更新为分包路径(/pages/pkg-mall/exchange/index 等) - TrendChart 组件优化 - 后台添加 admin_list_products API(支持查看已下架商品) - config/index.ts 添加 defineConstants 环境变量 - mp e2e check-readiness 路径修正
This commit is contained in:
116
apps/miniprogram/src/pages/pkg-profile/family-add/index.scss
Normal file
116
apps/miniprogram/src/pages/pkg-profile/family-add/index.scss
Normal file
@@ -0,0 +1,116 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.family-add-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 32px 24px;
|
||||
padding-bottom: 160px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@include section-title;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 4px 28px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28px 0;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
flex-shrink: 0;
|
||||
width: 140px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
flex: 1;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
text-align: right;
|
||||
border: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-placeholder {
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.form-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.form-picker-text {
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
margin-right: 10px;
|
||||
|
||||
&.placeholder {
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
.form-picker-arrow {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: $pri;
|
||||
padding: 28px;
|
||||
text-align: center;
|
||||
box-shadow: 0 -2px 12px rgba(196, 98, 58, 0.15);
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
131
apps/miniprogram/src/pages/pkg-profile/family-add/index.tsx
Normal file
131
apps/miniprogram/src/pages/pkg-profile/family-add/index.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, Input, Picker } from '@tarojs/components';
|
||||
import Taro, { useRouter } from '@tarojs/taro';
|
||||
import { createPatient, updatePatient, Patient } from '../../../services/patient';
|
||||
import './index.scss';
|
||||
|
||||
const RELATION_OPTIONS = ['本人', '配偶', '父母', '子女', '其他'];
|
||||
const GENDER_OPTIONS = ['男', '女'];
|
||||
|
||||
export default function FamilyAdd() {
|
||||
const router = useRouter();
|
||||
const editId = router.params.id || '';
|
||||
const editData = Taro.getStorageSync('edit_patient') as Patient | null;
|
||||
|
||||
const [name, setName] = useState(editData?.name || '');
|
||||
const [relationIdx, setRelationIdx] = useState(
|
||||
editData?.relation ? RELATION_OPTIONS.indexOf(editData.relation) : 0
|
||||
);
|
||||
const [genderIdx, setGenderIdx] = useState(
|
||||
editData?.gender === 'female' ? 1 : 0
|
||||
);
|
||||
const [birthDate, setBirthDate] = useState(editData?.birth_date || '');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
return () => { Taro.removeStorageSync('edit_patient'); };
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!name.trim()) {
|
||||
Taro.showToast({ title: '请输入姓名', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
if (editId && editData) {
|
||||
await updatePatient(editId, {
|
||||
name: name.trim(),
|
||||
gender: GENDER_OPTIONS[genderIdx] === '男' ? 'male' : 'female',
|
||||
birth_date: birthDate || undefined,
|
||||
relation: RELATION_OPTIONS[relationIdx],
|
||||
}, editData.version);
|
||||
Taro.showToast({ title: '修改成功', icon: 'success' });
|
||||
} else {
|
||||
await createPatient({
|
||||
name: name.trim(),
|
||||
gender: GENDER_OPTIONS[genderIdx] === '男' ? 'male' : 'female',
|
||||
birth_date: birthDate || undefined,
|
||||
});
|
||||
Taro.showToast({ title: '添加成功', icon: 'success' });
|
||||
}
|
||||
setTimeout(() => Taro.navigateBack(), 1000);
|
||||
} catch {
|
||||
Taro.showToast({ title: editId ? '修改失败' : '添加失败', icon: 'none' });
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='family-add-page'>
|
||||
<Text className='page-title'>{editId ? '编辑就诊人' : '添加就诊人'}</Text>
|
||||
|
||||
<View className='form-card'>
|
||||
<View className='form-item'>
|
||||
<Text className='form-label'>姓名</Text>
|
||||
<Input
|
||||
className='form-input'
|
||||
placeholder='请输入姓名'
|
||||
placeholderClass='form-placeholder'
|
||||
value={name}
|
||||
onInput={(e) => setName(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View className='form-item'>
|
||||
<Text className='form-label'>关系</Text>
|
||||
<Picker
|
||||
mode='selector'
|
||||
range={RELATION_OPTIONS}
|
||||
value={relationIdx}
|
||||
onChange={(e) => setRelationIdx(Number(e.detail.value))}
|
||||
>
|
||||
<View className='form-picker'>
|
||||
<Text className='form-picker-text'>{RELATION_OPTIONS[relationIdx]}</Text>
|
||||
<Text className='form-picker-arrow'>></Text>
|
||||
</View>
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
<View className='form-item'>
|
||||
<Text className='form-label'>性别</Text>
|
||||
<Picker
|
||||
mode='selector'
|
||||
range={GENDER_OPTIONS}
|
||||
value={genderIdx}
|
||||
onChange={(e) => setGenderIdx(Number(e.detail.value))}
|
||||
>
|
||||
<View className='form-picker'>
|
||||
<Text className='form-picker-text'>{GENDER_OPTIONS[genderIdx]}</Text>
|
||||
<Text className='form-picker-arrow'>></Text>
|
||||
</View>
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
<View className='form-item'>
|
||||
<Text className='form-label'>出生日期</Text>
|
||||
<Picker
|
||||
mode='date'
|
||||
value={birthDate || '2000-01-01'}
|
||||
onChange={(e) => setBirthDate(e.detail.value)}
|
||||
>
|
||||
<View className='form-picker'>
|
||||
<Text className={`form-picker-text ${!birthDate ? 'placeholder' : ''}`}>
|
||||
{birthDate || '请选择'}
|
||||
</Text>
|
||||
<Text className='form-picker-arrow'>></Text>
|
||||
</View>
|
||||
</Picker>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
className={`submit-btn ${submitting ? 'disabled' : ''}`}
|
||||
onClick={submitting ? undefined : handleSubmit}
|
||||
>
|
||||
<Text className='submit-text'>{submitting ? '提交中...' : editId ? '保存修改' : '确认添加'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
159
apps/miniprogram/src/pages/pkg-profile/family/index.scss
Normal file
159
apps/miniprogram/src/pages/pkg-profile/family/index.scss
Normal file
@@ -0,0 +1,159 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.family-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 32px 24px;
|
||||
padding-bottom: 160px;
|
||||
}
|
||||
|
||||
.family-page-title {
|
||||
@include section-title;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.family-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.family-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
box-shadow: $shadow-sm;
|
||||
transition: box-shadow 0.2s;
|
||||
|
||||
&:active {
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
|
||||
&.active {
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
}
|
||||
|
||||
.family-avatar {
|
||||
@include flex-center;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: $r;
|
||||
background: $pri-l;
|
||||
flex-shrink: 0;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.family-avatar-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: $pri-d;
|
||||
}
|
||||
|
||||
.family-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.family-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.family-name {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.family-current-tag {
|
||||
@include tag($pri, #fff);
|
||||
font-size: 18px;
|
||||
padding: 2px 10px;
|
||||
}
|
||||
|
||||
.family-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.family-relation-tag {
|
||||
@include tag($pri-l, $pri-d);
|
||||
font-size: 20px;
|
||||
padding: 2px 12px;
|
||||
}
|
||||
|
||||
.family-gender {
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.family-edit {
|
||||
flex-shrink: 0;
|
||||
margin-left: 16px;
|
||||
padding: 8px 20px;
|
||||
border: 1px solid $bd;
|
||||
border-radius: $r-pill;
|
||||
}
|
||||
|
||||
.family-edit-text {
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.family-add-btn {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: $pri;
|
||||
padding: 28px;
|
||||
text-align: center;
|
||||
box-shadow: 0 -2px 12px rgba(196, 98, 58, 0.15);
|
||||
}
|
||||
|
||||
.family-add-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
106
apps/miniprogram/src/pages/pkg-profile/family/index.tsx
Normal file
106
apps/miniprogram/src/pages/pkg-profile/family/index.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import { listPatients, Patient } from '../../../services/patient';
|
||||
import { useAuthStore } from '../../../stores/auth';
|
||||
import EmptyState from '../../../components/EmptyState';
|
||||
import './index.scss';
|
||||
|
||||
export default function FamilyList() {
|
||||
const [patients, setPatients] = useState<Patient[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { currentPatient, setCurrentPatient } = useAuthStore();
|
||||
|
||||
const fetchPatients = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await listPatients();
|
||||
setPatients(res.data || []);
|
||||
} catch {
|
||||
Taro.showToast({ title: '加载失败', icon: 'none' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidShow(() => {
|
||||
fetchPatients();
|
||||
});
|
||||
|
||||
const handleSelect = (patient: Patient) => {
|
||||
setCurrentPatient({
|
||||
id: patient.id,
|
||||
name: patient.name,
|
||||
gender: patient.gender,
|
||||
birth_date: patient.birth_date,
|
||||
relation: patient.relation || '本人',
|
||||
});
|
||||
Taro.showToast({ title: `已切换为 ${patient.name}`, icon: 'success' });
|
||||
};
|
||||
|
||||
const goToAdd = () => {
|
||||
Taro.navigateTo({ url: '/pages/pkg-profile/family-add/index' });
|
||||
};
|
||||
|
||||
const goToEdit = (patient: Patient) => {
|
||||
Taro.setStorageSync('edit_patient', patient);
|
||||
Taro.navigateTo({ url: `/pages/pkg-profile/family-add/index?id=${patient.id}` });
|
||||
};
|
||||
|
||||
const genderText = (g?: string) => {
|
||||
if (g === 'male') return '男';
|
||||
if (g === 'female') return '女';
|
||||
return '未知';
|
||||
};
|
||||
|
||||
const relationInitial = (relation: string) => {
|
||||
return relation ? relation.charAt(0) : '本';
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='family-page'>
|
||||
<Text className='family-page-title'>就诊人管理</Text>
|
||||
|
||||
<View className='family-list'>
|
||||
{patients.map((p) => {
|
||||
const isActive = currentPatient?.id === p.id;
|
||||
return (
|
||||
<View
|
||||
className={`family-item ${isActive ? 'active' : ''}`}
|
||||
key={p.id}
|
||||
onClick={() => handleSelect(p)}
|
||||
>
|
||||
<View className='family-avatar'>
|
||||
<Text className='family-avatar-text'>{relationInitial(p.relation || '本人')}</Text>
|
||||
</View>
|
||||
<View className='family-info'>
|
||||
<View className='family-name-row'>
|
||||
<Text className='family-name'>{p.name}</Text>
|
||||
{isActive && <Text className='family-current-tag'>当前</Text>}
|
||||
</View>
|
||||
<View className='family-meta'>
|
||||
<Text className='family-relation-tag'>{p.relation || '本人'}</Text>
|
||||
<Text className='family-gender'>{genderText(p.gender)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
className='family-edit'
|
||||
onClick={(e) => { e.stopPropagation(); goToEdit(p); }}
|
||||
>
|
||||
<Text className='family-edit-text'>编辑</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{patients.length === 0 && !loading && (
|
||||
<EmptyState text='暂无就诊人' />
|
||||
)}
|
||||
|
||||
<View className='family-add-btn' onClick={goToAdd}>
|
||||
<Text className='family-add-text'>添加就诊人</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
121
apps/miniprogram/src/pages/pkg-profile/followups/index.scss
Normal file
121
apps/miniprogram/src/pages/pkg-profile/followups/index.scss
Normal file
@@ -0,0 +1,121 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.my-followups-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
background: $card;
|
||||
padding: 0;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 24px 0 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 28px;
|
||||
color: $tx2;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.tab-item.active & {
|
||||
color: $pri;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-indicator {
|
||||
width: 32px;
|
||||
height: 4px;
|
||||
background: $pri;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.task-list {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.task-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
}
|
||||
|
||||
.task-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.task-name {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.task-status {
|
||||
@include tag($bd-l, $tx2);
|
||||
|
||||
&.pending {
|
||||
@include tag($wrn-l, $wrn);
|
||||
}
|
||||
|
||||
&.completed {
|
||||
@include tag($acc-l, $acc);
|
||||
}
|
||||
|
||||
&.overdue {
|
||||
@include tag($dan-l, $dan);
|
||||
}
|
||||
}
|
||||
|
||||
.task-desc {
|
||||
font-size: 26px;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-due {
|
||||
@include serif-number;
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
display: block;
|
||||
}
|
||||
103
apps/miniprogram/src/pages/pkg-profile/followups/index.tsx
Normal file
103
apps/miniprogram/src/pages/pkg-profile/followups/index.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import { listTasks, FollowUpTask } from '../../../services/followup';
|
||||
import EmptyState from '../../../components/EmptyState';
|
||||
import Loading from '../../../components/Loading';
|
||||
import './index.scss';
|
||||
|
||||
const TABS = [
|
||||
{ key: 'pending', label: '待完成' },
|
||||
{ key: 'completed', label: '已完成' },
|
||||
{ key: 'overdue', label: '已过期' },
|
||||
];
|
||||
|
||||
export default function MyFollowUps() {
|
||||
const [activeTab, setActiveTab] = useState('pending');
|
||||
const [tasks, setTasks] = useState<FollowUpTask[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchTasks = useCallback(async (status: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await listTasks(status);
|
||||
setTasks(res.data || []);
|
||||
} catch {
|
||||
Taro.showToast({ title: '加载失败', icon: 'none' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidShow(() => {
|
||||
fetchTasks(activeTab);
|
||||
});
|
||||
|
||||
const handleTabChange = (key: string) => {
|
||||
setActiveTab(key);
|
||||
fetchTasks(key);
|
||||
};
|
||||
|
||||
const goToDetail = (id: string) => {
|
||||
Taro.navigateTo({ url: `/pages/followup/detail/index?id=${id}` });
|
||||
};
|
||||
|
||||
const getStatusClass = (status: string) => {
|
||||
if (status === 'completed') return 'completed';
|
||||
if (status === 'overdue') return 'overdue';
|
||||
return 'pending';
|
||||
};
|
||||
|
||||
const getStatusLabel = (status: string) => {
|
||||
if (status === 'completed') return '已完成';
|
||||
if (status === 'overdue') return '已过期';
|
||||
return '待完成';
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='my-followups-page'>
|
||||
<View className='tab-bar'>
|
||||
{TABS.map((tab) => (
|
||||
<View
|
||||
className={`tab-item ${activeTab === tab.key ? 'active' : ''}`}
|
||||
key={tab.key}
|
||||
onClick={() => handleTabChange(tab.key)}
|
||||
>
|
||||
<Text className='tab-text'>{tab.label}</Text>
|
||||
{activeTab === tab.key && <View className='tab-indicator' />}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<View className='task-list'>
|
||||
{tasks.map((t) => (
|
||||
<View
|
||||
className='task-card'
|
||||
key={t.id}
|
||||
onClick={() => goToDetail(t.id)}
|
||||
>
|
||||
<View className='task-top'>
|
||||
<Text className='task-name'>{t.follow_up_type}</Text>
|
||||
<Text className={`task-status ${getStatusClass(t.status)}`}>
|
||||
{getStatusLabel(t.status)}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className='task-desc'>{t.content_template}</Text>
|
||||
<Text className='task-due'>截止: {t.planned_date}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{tasks.length === 0 && !loading && (
|
||||
<EmptyState text={`暂无${(() => {
|
||||
const tab = TABS.find((t) => t.key === activeTab);
|
||||
return tab ? tab.label : '';
|
||||
})()}任务`} />
|
||||
)}
|
||||
|
||||
{loading && (
|
||||
<Loading />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
260
apps/miniprogram/src/pages/pkg-profile/medication/index.scss
Normal file
260
apps/miniprogram/src/pages/pkg-profile/medication/index.scss
Normal file
@@ -0,0 +1,260 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.medication-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 32px 24px;
|
||||
padding-bottom: 160px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@include section-title;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.reminder-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.reminder-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 24px;
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.55;
|
||||
}
|
||||
}
|
||||
|
||||
.reminder-avatar {
|
||||
@include flex-center;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: $r;
|
||||
background: $acc-l;
|
||||
flex-shrink: 0;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.reminder-avatar-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: $acc;
|
||||
}
|
||||
|
||||
.reminder-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.reminder-name {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.reminder-dosage {
|
||||
@include serif-number;
|
||||
font-size: 24px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.reminder-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
width: 80px;
|
||||
height: 44px;
|
||||
border-radius: $r-pill;
|
||||
padding: 4px;
|
||||
position: relative;
|
||||
transition: background 0.3s;
|
||||
|
||||
&.on {
|
||||
background: $pri;
|
||||
}
|
||||
|
||||
&.off {
|
||||
background: $bd;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-dot {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
transition: left 0.3s;
|
||||
|
||||
.toggle.on & {
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
.toggle.off & {
|
||||
left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
font-size: 24px;
|
||||
color: $dan;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
margin-top: 24px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.form-card-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24px 0;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
flex-shrink: 0;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
flex: 1;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
text-align: right;
|
||||
border: none;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-placeholder {
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
.time-picker-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.time-value {
|
||||
@include serif-number;
|
||||
font-size: 28px;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.time-modify {
|
||||
font-size: 24px;
|
||||
color: $pri;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.form-cancel {
|
||||
flex: 1;
|
||||
background: $bd-l;
|
||||
border-radius: $r-sm;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-cancel-text {
|
||||
font-size: 28px;
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
.form-confirm {
|
||||
flex: 1;
|
||||
background: $pri;
|
||||
border-radius: $r-sm;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-confirm-text {
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: $pri;
|
||||
padding: 28px;
|
||||
text-align: center;
|
||||
box-shadow: 0 -2px 12px rgba(196, 98, 58, 0.15);
|
||||
}
|
||||
|
||||
.add-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 32px;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
175
apps/miniprogram/src/pages/pkg-profile/medication/index.tsx
Normal file
175
apps/miniprogram/src/pages/pkg-profile/medication/index.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, Text, Input, Picker } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import EmptyState from '../../../components/EmptyState';
|
||||
import './index.scss';
|
||||
|
||||
interface MedicationReminder {
|
||||
id: string;
|
||||
name: string;
|
||||
dosage: string;
|
||||
time: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'medication_reminders';
|
||||
|
||||
function loadReminders(): MedicationReminder[] {
|
||||
return Taro.getStorageSync(STORAGE_KEY) || [];
|
||||
}
|
||||
|
||||
function saveReminders(reminders: MedicationReminder[]) {
|
||||
Taro.setStorageSync(STORAGE_KEY, reminders);
|
||||
}
|
||||
|
||||
export default function MedicationReminder() {
|
||||
const [reminders, setReminders] = useState<MedicationReminder[]>([]);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [formName, setFormName] = useState('');
|
||||
const [formDosage, setFormDosage] = useState('');
|
||||
const [formTime, setFormTime] = useState('08:00');
|
||||
|
||||
useEffect(() => {
|
||||
setReminders(loadReminders());
|
||||
}, []);
|
||||
|
||||
const updateReminders = (updated: MedicationReminder[]) => {
|
||||
setReminders(updated);
|
||||
saveReminders(updated);
|
||||
};
|
||||
|
||||
const handleToggle = (id: string) => {
|
||||
const updated = reminders.map((r) =>
|
||||
r.id === id ? { ...r, enabled: !r.enabled } : r
|
||||
);
|
||||
updateReminders(updated);
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
Taro.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这个提醒吗?',
|
||||
}).then((res) => {
|
||||
if (res.confirm) {
|
||||
updateReminders(reminders.filter((r) => r.id !== id));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
if (!formName.trim()) {
|
||||
Taro.showToast({ title: '请输入药品名称', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const newReminder: MedicationReminder = {
|
||||
id: Date.now().toString(),
|
||||
name: formName.trim(),
|
||||
dosage: formDosage.trim(),
|
||||
time: formTime,
|
||||
enabled: true,
|
||||
};
|
||||
updateReminders([...reminders, newReminder]);
|
||||
setFormName('');
|
||||
setFormDosage('');
|
||||
setFormTime('08:00');
|
||||
setShowForm(false);
|
||||
Taro.showToast({ title: '添加成功', icon: 'success' });
|
||||
};
|
||||
|
||||
const nameInitial = (name: string) => {
|
||||
return name ? name.charAt(0) : '药';
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='medication-page'>
|
||||
<Text className='page-title'>用药提醒</Text>
|
||||
|
||||
<View className='reminder-list'>
|
||||
{reminders.map((r) => (
|
||||
<View className={`reminder-card ${!r.enabled ? 'disabled' : ''}`} key={r.id}>
|
||||
<View className='reminder-avatar'>
|
||||
<Text className='reminder-avatar-text'>{nameInitial(r.name)}</Text>
|
||||
</View>
|
||||
<View className='reminder-info'>
|
||||
<Text className='reminder-name'>{r.name}</Text>
|
||||
<Text className='reminder-dosage'>
|
||||
{r.dosage} | {r.time}
|
||||
</Text>
|
||||
</View>
|
||||
<View className='reminder-actions'>
|
||||
<View
|
||||
className={`toggle ${r.enabled ? 'on' : 'off'}`}
|
||||
onClick={() => handleToggle(r.id)}
|
||||
>
|
||||
<View className='toggle-dot' />
|
||||
</View>
|
||||
<Text
|
||||
className='delete-btn'
|
||||
onClick={() => handleDelete(r.id)}
|
||||
>
|
||||
删除
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{reminders.length === 0 && (
|
||||
<EmptyState text='暂无用药提醒' />
|
||||
)}
|
||||
|
||||
{showForm && (
|
||||
<View className='form-card'>
|
||||
<Text className='form-card-title'>添加提醒</Text>
|
||||
<View className='form-item'>
|
||||
<Text className='form-label'>药品名称</Text>
|
||||
<Input
|
||||
className='form-input'
|
||||
placeholder='请输入药品名称'
|
||||
placeholderClass='form-placeholder'
|
||||
value={formName}
|
||||
onInput={(e) => setFormName(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
<View className='form-item'>
|
||||
<Text className='form-label'>剂量</Text>
|
||||
<Input
|
||||
className='form-input'
|
||||
placeholder='如: 1片、10ml'
|
||||
placeholderClass='form-placeholder'
|
||||
value={formDosage}
|
||||
onInput={(e) => setFormDosage(e.detail.value)}
|
||||
/>
|
||||
</View>
|
||||
<View className='form-item'>
|
||||
<Text className='form-label'>提醒时间</Text>
|
||||
<Picker
|
||||
mode='time'
|
||||
value={formTime}
|
||||
onChange={(e) => setFormTime(e.detail.value)}
|
||||
>
|
||||
<View className='time-picker-wrap'>
|
||||
<Text className='time-value'>{formTime}</Text>
|
||||
<Text className='time-modify'>修改</Text>
|
||||
</View>
|
||||
</Picker>
|
||||
</View>
|
||||
<View className='form-actions'>
|
||||
<View className='form-cancel' onClick={() => setShowForm(false)}>
|
||||
<Text className='form-cancel-text'>取消</Text>
|
||||
</View>
|
||||
<View className='form-confirm' onClick={handleAdd}>
|
||||
<Text className='form-confirm-text'>确认</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{!showForm && (
|
||||
<View className='add-btn' onClick={() => setShowForm(true)}>
|
||||
<Text className='add-text'>添加提醒</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
116
apps/miniprogram/src/pages/pkg-profile/reports/index.scss
Normal file
116
apps/miniprogram/src/pages/pkg-profile/reports/index.scss
Normal file
@@ -0,0 +1,116 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin serif-number {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@mixin tag($bg, $color) {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
background: $bg;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.my-reports-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 32px 24px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@include section-title;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.report-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.report-card {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 28px;
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
}
|
||||
|
||||
.report-card-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.report-type-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.report-avatar {
|
||||
@include flex-center;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: $r-sm;
|
||||
background: $pri-l;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.report-avatar-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: $pri-d;
|
||||
}
|
||||
|
||||
.report-type {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.report-status {
|
||||
@include tag($bd-l, $tx2);
|
||||
|
||||
&.normal {
|
||||
@include tag($acc-l, $acc);
|
||||
}
|
||||
|
||||
&.abnormal {
|
||||
@include tag($dan-l, $dan);
|
||||
}
|
||||
}
|
||||
|
||||
.report-date {
|
||||
@include serif-number;
|
||||
font-size: 26px;
|
||||
color: $tx2;
|
||||
display: block;
|
||||
padding-left: 72px;
|
||||
}
|
||||
106
apps/miniprogram/src/pages/pkg-profile/reports/index.tsx
Normal file
106
apps/miniprogram/src/pages/pkg-profile/reports/index.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useDidShow, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
|
||||
import { listReports, LabReport } from '../../../services/report';
|
||||
import EmptyState from '../../../components/EmptyState';
|
||||
import Loading from '../../../components/Loading';
|
||||
import './index.scss';
|
||||
|
||||
export default function MyReports() {
|
||||
const [reports, setReports] = useState<LabReport[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchData = useCallback(async (p: number, append = false) => {
|
||||
const patientId = Taro.getStorageSync('current_patient_id') || '';
|
||||
if (!patientId) {
|
||||
setReports([]);
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await listReports(patientId, p);
|
||||
const list = res.data || [];
|
||||
setReports(append ? (prev) => [...prev, ...list] : list);
|
||||
setTotal(res.total);
|
||||
setPage(p);
|
||||
} catch {
|
||||
Taro.showToast({ title: '加载失败', icon: 'none' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidShow(() => {
|
||||
fetchData(1);
|
||||
});
|
||||
|
||||
usePullDownRefresh(() => {
|
||||
fetchData(1).finally(() => {
|
||||
Taro.stopPullDownRefresh();
|
||||
});
|
||||
});
|
||||
|
||||
useReachBottom(() => {
|
||||
if (!loading && reports.length < total) {
|
||||
fetchData(page + 1, true);
|
||||
}
|
||||
});
|
||||
|
||||
const goToDetail = (id: string) => {
|
||||
Taro.navigateTo({ url: `/pages/report/detail/index?id=${id}` });
|
||||
};
|
||||
|
||||
const formatStatus = (report: LabReport) => {
|
||||
const indicators = report.indicators;
|
||||
if (!indicators || typeof indicators !== 'object') return 'unknown';
|
||||
const vals = Object.values(indicators) as Array<{ status?: string }>;
|
||||
const hasAbnormal = vals.some((v) => v.status === 'high' || v.status === 'low');
|
||||
return hasAbnormal ? 'abnormal' : 'normal';
|
||||
};
|
||||
|
||||
const typeInitial = (type: string) => {
|
||||
return type ? type.charAt(0) : '报';
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='my-reports-page'>
|
||||
<Text className='page-title'>检查报告</Text>
|
||||
|
||||
<View className='report-list'>
|
||||
{reports.map((r) => {
|
||||
const status = formatStatus(r);
|
||||
return (
|
||||
<View
|
||||
className='report-card'
|
||||
key={r.id}
|
||||
onClick={() => goToDetail(r.id)}
|
||||
>
|
||||
<View className='report-card-top'>
|
||||
<View className='report-type-row'>
|
||||
<View className='report-avatar'>
|
||||
<Text className='report-avatar-text'>{typeInitial(r.report_type)}</Text>
|
||||
</View>
|
||||
<Text className='report-type'>{r.report_type}</Text>
|
||||
</View>
|
||||
<Text className={`report-status ${status}`}>
|
||||
{status === 'normal' ? '正常' : status === 'abnormal' ? '异常' : '未知'}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className='report-date'>{r.report_date}</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{reports.length === 0 && !loading && (
|
||||
<EmptyState text={Taro.getStorageSync('current_patient_id') ? '暂无报告记录' : '请先在就诊人管理中选择就诊人'} />
|
||||
)}
|
||||
|
||||
{loading && (
|
||||
<Loading />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
85
apps/miniprogram/src/pages/pkg-profile/settings/index.scss
Normal file
85
apps/miniprogram/src/pages/pkg-profile/settings/index.scss
Normal file
@@ -0,0 +1,85 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
@mixin section-title {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: $tx;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.settings-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@include section-title;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
overflow: hidden;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
|
||||
.settings-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28px 24px;
|
||||
border-bottom: 1px solid $bd-l;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.logout-item {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-icon {
|
||||
@include flex-center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: $r-sm;
|
||||
background: $pri-l;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.settings-icon-text {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: $pri-d;
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
flex: 1;
|
||||
font-size: 30px;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.logout-label {
|
||||
color: $dan;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.settings-arrow {
|
||||
font-size: 24px;
|
||||
color: $tx3;
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
96
apps/miniprogram/src/pages/pkg-profile/settings/index.tsx
Normal file
96
apps/miniprogram/src/pages/pkg-profile/settings/index.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import React from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useAuthStore } from '../../../stores/auth';
|
||||
import './index.scss';
|
||||
|
||||
export default function Settings() {
|
||||
const { logout } = useAuthStore();
|
||||
|
||||
const handleClearCache = () => {
|
||||
Taro.showModal({
|
||||
title: '清除缓存',
|
||||
content: '确定要清除本地缓存数据吗?不会影响账号信息。',
|
||||
}).then((res) => {
|
||||
if (res.confirm) {
|
||||
const preservedKeys = ['access_token', 'refresh_token', 'user_data', 'user_roles', 'tenant_id', 'wechat_openid', 'current_patient', 'current_patient_id'];
|
||||
const preservedData: Record<string, unknown> = {};
|
||||
for (const key of preservedKeys) {
|
||||
const val = Taro.getStorageSync(key);
|
||||
if (val) preservedData[key] = val;
|
||||
}
|
||||
|
||||
Taro.clearStorageSync();
|
||||
|
||||
for (const [key, val] of Object.entries(preservedData)) {
|
||||
Taro.setStorageSync(key, val);
|
||||
}
|
||||
|
||||
Taro.showToast({ title: '缓存已清除', icon: 'success' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleAbout = () => {
|
||||
Taro.showModal({
|
||||
title: '关于我们',
|
||||
content: 'HMS 健康管理平台 v1.0.0\n为您的健康保驾护航',
|
||||
showCancel: false,
|
||||
});
|
||||
};
|
||||
|
||||
const handlePrivacy = () => {
|
||||
Taro.showModal({
|
||||
title: '隐私政策',
|
||||
content: '我们重视您的隐私保护。所有健康数据均经过加密存储,仅用于为您提供健康管理服务,不会向第三方分享。',
|
||||
showCancel: false,
|
||||
});
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
Taro.showModal({
|
||||
title: '退出登录',
|
||||
content: '确定要退出登录吗?',
|
||||
}).then((res) => {
|
||||
if (res.confirm) {
|
||||
logout();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='settings-page'>
|
||||
<Text className='page-title'>设置</Text>
|
||||
|
||||
<View className='settings-group'>
|
||||
<View className='settings-item' onClick={handleClearCache}>
|
||||
<View className='settings-icon'>
|
||||
<Text className='settings-icon-text'>缓</Text>
|
||||
</View>
|
||||
<Text className='settings-label'>清除缓存</Text>
|
||||
<Text className='settings-arrow'>></Text>
|
||||
</View>
|
||||
<View className='settings-item' onClick={handleAbout}>
|
||||
<View className='settings-icon'>
|
||||
<Text className='settings-icon-text'>关</Text>
|
||||
</View>
|
||||
<Text className='settings-label'>关于我们</Text>
|
||||
<Text className='settings-arrow'>></Text>
|
||||
</View>
|
||||
<View className='settings-item' onClick={handlePrivacy}>
|
||||
<View className='settings-icon'>
|
||||
<Text className='settings-icon-text'>隐</Text>
|
||||
</View>
|
||||
<Text className='settings-label'>隐私政策</Text>
|
||||
<Text className='settings-arrow'>></Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='settings-group'>
|
||||
<View className='settings-item logout-item' onClick={handleLogout}>
|
||||
<Text className='settings-label logout-label'>退出登录</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user