feat:新增病历列表页面
This commit is contained in:
parent
e838f8af15
commit
594913404b
42
pages.json
42
pages.json
@ -12,6 +12,48 @@
|
||||
"navigationBarTitleText": "病例"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/case/search",
|
||||
"style": {
|
||||
"navigationBarTitleText": "搜索患者"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/case/group-manage",
|
||||
"style": {
|
||||
"navigationBarTitleText": "分组管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/case/batch-transfer",
|
||||
"style": {
|
||||
"navigationBarTitleText": "转移客户给其他团队"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/case/batch-share",
|
||||
"style": {
|
||||
"navigationBarTitleText": "共享客户"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/case/patient-invite",
|
||||
"style": {
|
||||
"navigationBarTitleText": "邀请患者"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/case/patient-create",
|
||||
"style": {
|
||||
"navigationBarTitleText": "新增患者"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/case/patient-inner-info",
|
||||
"style": {
|
||||
"navigationBarTitleText": "内部信息"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/work/work",
|
||||
"style": {
|
||||
|
||||
173
pages/case/batch-share.vue
Normal file
173
pages/case/batch-share.vue
Normal file
@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<view class="share-container">
|
||||
<view class="content">
|
||||
<view class="section-title">选择共享团队</view>
|
||||
<view class="selector-item" @click="selectTeam">
|
||||
<text :class="team ? '' : 'placeholder'">{{ team ? team.name : "请选择团队" }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
|
||||
<view class="tips">共享客户:表示客户档案共享多个团队可见,多个团队可同时为该客户服务。</view>
|
||||
|
||||
<template v-if="team">
|
||||
<view class="section-title">选择责任人</view>
|
||||
<view class="selector-item" @click="selectUser">
|
||||
<text :class="user ? '' : 'placeholder'">{{ user ? user.name : "请选择责任人" }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<button class="btn plain" @click="cancel">取消</button>
|
||||
<button class="btn primary" @click="save">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const team = ref(null);
|
||||
const user = ref(null);
|
||||
|
||||
// Mock Data
|
||||
const teams = [
|
||||
{ id: 1, name: '李医生团队' },
|
||||
{ id: 2, name: '王医生团队' }
|
||||
];
|
||||
|
||||
const users = [
|
||||
{ id: 101, name: '张医生' },
|
||||
{ id: 102, name: '李医生' },
|
||||
{ id: 103, name: '王医生' }
|
||||
];
|
||||
|
||||
const selectTeam = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: teams.map(t => t.name),
|
||||
success: (res) => {
|
||||
team.value = teams[res.tapIndex];
|
||||
user.value = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const selectUser = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: users.map(u => u.name),
|
||||
success: (res) => {
|
||||
user.value = users[res.tapIndex];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
if (!team.value) {
|
||||
uni.showToast({ title: '请选择团队', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!user.value) {
|
||||
uni.showToast({ title: '请选择负责人', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '保存中' });
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '操作成功' });
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.share-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.selector-item {
|
||||
background-color: #fff;
|
||||
height: 44px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px;
|
||||
|
||||
text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
|
||||
&.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #fff;
|
||||
padding: 15px 20px 30px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
|
||||
&.plain {
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: #5d8aff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
174
pages/case/batch-transfer.vue
Normal file
174
pages/case/batch-transfer.vue
Normal file
@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<view class="transfer-container">
|
||||
<view class="content">
|
||||
<view class="section-title">选择新负责团队</view>
|
||||
<view class="selector-item" @click="selectTeam">
|
||||
<text :class="team ? '' : 'placeholder'">{{ team ? team.name : "请选择选择团队" }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
|
||||
<template v-if="team">
|
||||
<view class="section-title">选择责任人</view>
|
||||
<view class="selector-item" @click="selectUser">
|
||||
<text :class="user ? '' : 'placeholder'">{{ user ? user.name : "请选择责任人" }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="tips">客户将与本团队解除服务关系,本团队成员将没有权限查询到客户档案。</view>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<button class="btn plain" @click="cancel">取消</button>
|
||||
<button class="btn primary" @click="save">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const team = ref(null);
|
||||
const user = ref(null);
|
||||
|
||||
// Mock Data
|
||||
const teams = [
|
||||
{ id: 1, name: '张敏西服务团队' },
|
||||
{ id: 2, name: '李医生团队' },
|
||||
{ id: 3, name: '王医生团队' }
|
||||
];
|
||||
|
||||
const users = [
|
||||
{ id: 101, name: '张医生' },
|
||||
{ id: 102, name: '李医生' },
|
||||
{ id: 103, name: '王医生' }
|
||||
];
|
||||
|
||||
const selectTeam = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: teams.map(t => t.name),
|
||||
success: (res) => {
|
||||
team.value = teams[res.tapIndex];
|
||||
user.value = null; // Reset user when team changes
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const selectUser = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: users.map(u => u.name),
|
||||
success: (res) => {
|
||||
user.value = users[res.tapIndex];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
if (!team.value) {
|
||||
uni.showToast({ title: '请选择团队', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!user.value) {
|
||||
uni.showToast({ title: '请选择责任人', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '保存中' });
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '操作成功' });
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.transfer-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.selector-item {
|
||||
background-color: #fff;
|
||||
height: 44px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px;
|
||||
|
||||
text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
|
||||
&.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 15px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #fff;
|
||||
padding: 15px 20px 30px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
|
||||
&.plain {
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: #5d8aff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,8 +1,724 @@
|
||||
<template>
|
||||
<div>case</div>
|
||||
<view class="container">
|
||||
<!-- Header -->
|
||||
<view class="header">
|
||||
<view class="team-selector" @click="toggleTeamPopup">
|
||||
<text class="team-name">{{ teamDisplay }}</text>
|
||||
<uni-icons type="loop" size="18" color="#333" class="team-icon"></uni-icons>
|
||||
</view>
|
||||
<view class="header-actions">
|
||||
<view class="action-item" @click="goToSearch">
|
||||
<uni-icons type="search" size="22" color="#333"></uni-icons>
|
||||
<text class="action-text">搜索</text>
|
||||
</view>
|
||||
<view class="action-item" @click="toggleBatchMode">
|
||||
<uni-icons type="checkbox" size="22" color="#333"></uni-icons>
|
||||
<text class="action-text">批量</text>
|
||||
</view>
|
||||
<view class="action-item" @click="goToGroupManage">
|
||||
<uni-icons type="staff" size="22" color="#333"></uni-icons>
|
||||
<text class="action-text">分组</text>
|
||||
</view>
|
||||
<view class="action-item" @click="handleCreate">
|
||||
<uni-icons type="plusempty" size="22" color="#333"></uni-icons>
|
||||
<text class="action-text">新增</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Tabs and Count -->
|
||||
<view class="tabs-area">
|
||||
<scroll-view scroll-x class="tabs-scroll" :show-scrollbar="false">
|
||||
<view class="tabs-container">
|
||||
<view
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === index }"
|
||||
@click="currentTab = index"
|
||||
>
|
||||
{{ tab }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="total-count-inline">共{{ totalPatients }}条</view>
|
||||
</view>
|
||||
|
||||
<!-- Main Content -->
|
||||
<view class="content-body">
|
||||
<!-- Patient List -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="patient-list"
|
||||
:scroll-into-view="scrollIntoId"
|
||||
:scroll-with-animation="true"
|
||||
>
|
||||
<view v-for="group in patientList" :key="group.letter" :id="'letter-' + group.letter">
|
||||
<view class="group-title">{{ group.letter }}</view>
|
||||
|
||||
<view v-for="(patient, pIndex) in group.data" :key="pIndex" class="patient-card" @click="isBatchMode ? toggleSelect(patient) : null">
|
||||
<!-- Checkbox for Batch Mode -->
|
||||
<view v-if="isBatchMode" class="checkbox-area">
|
||||
<uni-icons
|
||||
:type="selectedItems.includes(patient.phone) ? 'checkbox-filled' : 'checkbox'"
|
||||
size="24"
|
||||
:color="selectedItems.includes(patient.phone) ? '#007aff' : '#ccc'"
|
||||
></uni-icons>
|
||||
</view>
|
||||
<view class="card-content">
|
||||
<!-- Row 1 -->
|
||||
<view class="card-row-top">
|
||||
<view class="patient-info">
|
||||
<text class="patient-name">{{ patient.name }}</text>
|
||||
<text class="patient-meta">{{ patient.gender }}/{{ patient.age }}岁</text>
|
||||
</view>
|
||||
<view class="patient-tags">
|
||||
<view v-for="(tag, tIndex) in patient.tags" :key="tIndex" class="tag">
|
||||
{{ tag }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Row 2 / 3 -->
|
||||
<view class="card-row-bottom">
|
||||
<template v-if="currentTab === 1"> <!-- New Patient Tab -->
|
||||
<text class="record-text">
|
||||
{{ patient.createTime || '-' }} / {{ patient.creator || '-' }}
|
||||
</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text v-if="patient.record" class="record-text">
|
||||
{{ patient.record.type }} / {{ patient.record.date }} / {{ patient.record.diagnosis }}
|
||||
</text>
|
||||
<text v-else class="no-record">暂无病历记录</text>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- Bottom padding for tabbar -->
|
||||
<view style="height: 100px;"></view> <!-- Increased padding -->
|
||||
</scroll-view>
|
||||
|
||||
<!-- Sidebar Index -->
|
||||
<view v-if="!isBatchMode" class="sidebar-index">
|
||||
<view
|
||||
v-for="letter in indexList"
|
||||
:key="letter"
|
||||
class="index-item"
|
||||
@click="scrollToLetter(letter)"
|
||||
>
|
||||
{{ letter }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Batch Actions Footer -->
|
||||
<view v-if="isBatchMode" class="batch-footer">
|
||||
<view class="left-action" @click="handleSelectAll">
|
||||
<uni-icons
|
||||
:type="selectedItems.length > 0 && selectedItems.length === patientList.flatMap(g => g.data).length ? 'checkbox-filled' : 'checkbox'"
|
||||
size="24"
|
||||
color="#666"
|
||||
></uni-icons>
|
||||
<text class="footer-text">全选 ({{ selectedItems.length }})</text>
|
||||
</view>
|
||||
<view class="right-actions">
|
||||
<button class="footer-btn plain" @click="cancelBatch">取消</button>
|
||||
<button class="footer-btn primary" @click="handleTransfer">转移</button>
|
||||
<button class="footer-btn primary" @click="handleShare">共享</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
// State
|
||||
const currentTeam = ref('张敏西服务团队');
|
||||
const currentTab = ref(0);
|
||||
const scrollIntoId = ref('');
|
||||
const tabs = ['全部', '新患者', '糖尿病', '高血压', '冠心病', '慢阻肺'];
|
||||
const isBatchMode = ref(false);
|
||||
const selectedItems = ref([]); // Stores patient phone or unique ID
|
||||
|
||||
// 新增流程所需状态(后续接接口替换)
|
||||
const managedArchiveCountAllTeams = ref(10); // 在管档案数(所有团队)
|
||||
const isVerified = ref(true); // 是否已认证
|
||||
const hasVerifyFailedHistory = ref(false); // 是否有历史认证失败
|
||||
const verifyFailedReason = ref('资料不完整,请补充营业执照/资质证明后重新提交。');
|
||||
|
||||
const teamDisplay = computed(() => `${currentTeam.value}(${managedArchiveCountAllTeams.value})`);
|
||||
|
||||
// Mock Data
|
||||
const allPatients = [
|
||||
{
|
||||
letter: 'A',
|
||||
data: [
|
||||
{
|
||||
name: '安乐', gender: '男', age: 45, tags: ['糖尿病'],
|
||||
record: { type: '门诊', date: '2026.1.10', diagnosis: '2型糖尿病' },
|
||||
createTime: '2026.1.19 14:30', creator: '李医生', phone: '13888888888', hospitalId: '1001'
|
||||
},
|
||||
{
|
||||
name: '奥利奥', gender: '女', age: 22, tags: [], record: null,
|
||||
createTime: '2026.1.15 09:00', creator: '王医生', phone: '13999999999', hospitalId: '1002'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
letter: 'L',
|
||||
data: [
|
||||
{
|
||||
name: '李珊珊', gender: '女', age: 37, tags: ['糖尿病', '高血压'],
|
||||
record: { type: '门诊', date: '2026.1.10', diagnosis: '急性上呼吸道感染' },
|
||||
createTime: '2026.1.10 10:20', creator: '张医生', phone: '13666666666', hospitalId: '1003'
|
||||
},
|
||||
{
|
||||
name: '李珊珊', gender: '女', age: 37, tags: [],
|
||||
record: { type: '住院', date: '2026.1.10', diagnosis: '急性上呼吸道感染' },
|
||||
createTime: '2025.12.30 11:00', creator: '张医生', phone: '13666666667', hospitalId: '10031'
|
||||
},
|
||||
{
|
||||
name: '李某某', gender: '女', age: 37, tags: [], record: null,
|
||||
createTime: '2025.12.01 08:30', creator: '系统导入', phone: '13555555555', hospitalId: '1004'
|
||||
},
|
||||
{
|
||||
name: '李四', gender: '男', age: 50, tags: ['高血压'], record: null,
|
||||
createTime: '2026.1.18 16:45', creator: '管理员', phone: '13444444444', hospitalId: '1005'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
letter: 'Z',
|
||||
data: [
|
||||
{
|
||||
name: '张三', gender: '男', age: 28, tags: [], record: null,
|
||||
createTime: '2026.1.19 10:00', creator: '赵医生', phone: '13333333333', hospitalId: '1006'
|
||||
},
|
||||
{
|
||||
name: '张敏', gender: '女', age: 32, tags: ['高血压'],
|
||||
record: { type: '门诊', date: '2025.12.15', diagnosis: '高血压' },
|
||||
createTime: '2025.11.20 15:15', creator: '孙医生', phone: '13222222222', hospitalId: '1007'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Computed
|
||||
const patientList = computed(() => {
|
||||
let list = allPatients;
|
||||
|
||||
// New Patient Filter (Last 7 days)
|
||||
if (currentTab.value === 1) {
|
||||
// Current date logic: 2026-01-20
|
||||
const now = new Date('2026-01-20').getTime();
|
||||
const sevenDaysAgo = now - 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
let flatList = [];
|
||||
list.forEach(group => {
|
||||
group.data.forEach(p => {
|
||||
if (p.createTime) {
|
||||
// Parse format 2026.1.19 14:30
|
||||
const timeStr = p.createTime.replace(/\./g, '/'); // safari compatibility often needs /, but for calculation standardizing
|
||||
const pTime = new Date(timeStr).getTime();
|
||||
if (pTime >= sevenDaysAgo) {
|
||||
flatList.push({ ...p, _ts: pTime });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Sort by create time desc
|
||||
flatList.sort((a, b) => b._ts - a._ts);
|
||||
|
||||
// Re-group or just show one group?
|
||||
// Usually grouped list maintains grouping or creates a "Search Result" group.
|
||||
// Requirement says "New Patient show...". Usually lists are still grouped by alphabet or just a flat list.
|
||||
// The requirement "Sorted from late to early" for creation time implies order is important, creating letter groups breaks time order.
|
||||
// So for "New Patient" tab, we should probably output a single group or regoup by date?
|
||||
// Requirement 1.2 "New Patient card... order late to early". This strongly implies creating a single list sorted by time.
|
||||
// But the template expects `group.letter` and `group.data`.
|
||||
// I will return a single group named 'Recent' or similar, or just empty letter to hide header.
|
||||
// The template has `<view class="group-title">{{ group.letter }}</view>`. If letter is empty, it shows empty line.
|
||||
// I will return [{ letter: '新患者', data: flatList }]
|
||||
|
||||
return [{ letter: '最近新增', data: flatList }];
|
||||
}
|
||||
|
||||
// Tab Filtering (Mock logic for other tabs)
|
||||
if (currentTab.value > 1) {
|
||||
const tabName = tabs[currentTab.value];
|
||||
list = list.map(group => ({
|
||||
...group,
|
||||
data: group.data.filter(p => p.tags.includes(tabName))
|
||||
})).filter(group => group.data.length > 0);
|
||||
}
|
||||
|
||||
return list;
|
||||
});
|
||||
|
||||
const indexList = computed(() => {
|
||||
if (currentTab.value === 1) return []; // No index bar for new patient
|
||||
return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').filter(l => patientList.value.some(g => g.letter === l));
|
||||
});
|
||||
|
||||
const totalPatients = computed(() => {
|
||||
let count = 0;
|
||||
patientList.value.forEach(g => count += g.data.length);
|
||||
return count;
|
||||
});
|
||||
|
||||
// Methods
|
||||
const checkBatchMode = () => {
|
||||
if (isBatchMode.value) {
|
||||
uni.showToast({ title: '请先完成当前批量设置或点击底部“取消”按钮退出', icon: 'none' });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const scrollToLetter = (letter) => {
|
||||
if (currentTab.value === 1) return;
|
||||
scrollIntoId.value = 'letter-' + letter;
|
||||
};
|
||||
|
||||
const toggleTeamPopup = () => {
|
||||
if (checkBatchMode()) return;
|
||||
uni.showActionSheet({
|
||||
itemList: ['张敏西服务团队', '李医生团队', '王医生团队'],
|
||||
success: function (res) {
|
||||
const teams = ['张敏西服务团队', '李医生团队', '王医生团队'];
|
||||
currentTeam.value = teams[res.tapIndex];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const goToSearch = () => {
|
||||
if (checkBatchMode()) return;
|
||||
uni.navigateTo({
|
||||
url: '/pages/case/search'
|
||||
});
|
||||
};
|
||||
|
||||
const goToGroupManage = () => {
|
||||
if (checkBatchMode()) return;
|
||||
uni.navigateTo({
|
||||
url: '/pages/case/group-manage'
|
||||
});
|
||||
};
|
||||
|
||||
const toggleBatchMode = () => {
|
||||
if (isBatchMode.value) {
|
||||
// Already in batch mode, do nothing or prompt?
|
||||
// Prompt says "click Other operations... prompt please finish".
|
||||
// Clicking "Batch" itself while in batch mode: user usually expects toggle off or nothing.
|
||||
// Based on "click Cancel button to exit", I'll assume clicking existing batch button doesn't exit.
|
||||
return;
|
||||
}
|
||||
isBatchMode.value = true;
|
||||
selectedItems.value = [];
|
||||
};
|
||||
|
||||
const handleCreate = () => {
|
||||
if (checkBatchMode()) return;
|
||||
// 100上限:无法继续新增 -> 引导联系客服(预留入口)
|
||||
if (managedArchiveCountAllTeams.value >= 100) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '当前管理档案数已达 100 个,无法继续新增。如需提升档案管理数,请联系客服处理。',
|
||||
cancelText: '知道了',
|
||||
confirmText: '添加客服',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
openAddCustomerServiceEntry();
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 未认证 + 达到10上限:提示去认证
|
||||
if (!isVerified.value && managedArchiveCountAllTeams.value >= 10) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '当前管理档案数已达上限 10 个,完成认证即可升级至 100 个。',
|
||||
cancelText: '暂不认证',
|
||||
confirmText: '去认证',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
startVerifyFlow();
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 未达上限:显示新增入口
|
||||
uni.showActionSheet({
|
||||
itemList: ['邀请患者建档', '我帮患者建档'],
|
||||
success: (res) => {
|
||||
if (res.tapIndex === 0) {
|
||||
openInvitePatientEntry();
|
||||
} else if (res.tapIndex === 1) {
|
||||
openCreatePatientEntry();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 新增流程:认证分支
|
||||
const startVerifyFlow = () => {
|
||||
// 有历史失败记录 -> 展示失败原因 & 重新认证
|
||||
if (hasVerifyFailedHistory.value) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: `您有历史认证未通过记录。失败原因为:\n\n${verifyFailedReason.value}`,
|
||||
cancelText: '取消',
|
||||
confirmText: '重新认证',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
openVerifyEntry();
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 正常去认证
|
||||
openVerifyEntry();
|
||||
};
|
||||
|
||||
// ===== 预留入口(后续对接真实页面/接口) =====
|
||||
const openVerifyEntry = () => {
|
||||
uni.showToast({ title: '认证功能待接入', icon: 'none' });
|
||||
};
|
||||
|
||||
const openAddCustomerServiceEntry = () => {
|
||||
uni.showToast({ title: '添加客服功能待接入', icon: 'none' });
|
||||
};
|
||||
|
||||
const openInvitePatientEntry = () => {
|
||||
uni.navigateTo({ url: '/pages/case/patient-invite' });
|
||||
};
|
||||
|
||||
const openCreatePatientEntry = () => {
|
||||
uni.navigateTo({ url: '/pages/case/patient-create' });
|
||||
};
|
||||
|
||||
// Batch Operations
|
||||
const toggleSelect = (patient) => {
|
||||
if (!isBatchMode.value) return; // Should not happen if click handler is correct
|
||||
|
||||
const id = patient.phone; // Using phone as unique ID for mock
|
||||
const index = selectedItems.value.indexOf(id);
|
||||
if (index > -1) {
|
||||
selectedItems.value.splice(index, 1);
|
||||
} else {
|
||||
selectedItems.value.push(id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
// Flatten current list
|
||||
const currentList = patientList.value.flatMap(group => group.data);
|
||||
if (selectedItems.value.length === currentList.length) {
|
||||
selectedItems.value = []; // Unselect All
|
||||
} else {
|
||||
selectedItems.value = currentList.map(p => p.phone);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelBatch = () => {
|
||||
isBatchMode.value = false;
|
||||
selectedItems.value = [];
|
||||
};
|
||||
|
||||
const handleTransfer = () => {
|
||||
if (selectedItems.value.length === 0) {
|
||||
uni.showToast({ title: '请选择患者', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
// Navigate to Transfer Page
|
||||
uni.navigateTo({ url: '/pages/case/batch-transfer' });
|
||||
};
|
||||
|
||||
const handleShare = () => {
|
||||
if (selectedItems.value.length === 0) {
|
||||
uni.showToast({ title: '请选择患者', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
// Navigate to Share Page
|
||||
uni.navigateTo({ url: '/pages/case/batch-share' });
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #fff;
|
||||
padding-bottom: 0; // Default
|
||||
|
||||
// Padding for batch footer
|
||||
/* &.is-batch {
|
||||
padding-bottom: 50px;
|
||||
} */
|
||||
// We can't use &.is-batch because scoped style and root element is tricky depending on uni-app version/style
|
||||
// Instead we handle it in content-body or separate view
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.team-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
|
||||
.team-name {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.action-text {
|
||||
font-size: 10px;
|
||||
color: #333;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f7fa;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-right: 15px; // Padding for the count
|
||||
|
||||
.tabs-scroll {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
.tabs-container {
|
||||
display: flex;
|
||||
padding: 10px 15px;
|
||||
|
||||
.tab-item {
|
||||
padding: 5px 15px;
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.active {
|
||||
color: #5d8aff;
|
||||
background-color: #e6f0ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.total-count-inline {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
min-width: 50px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.content-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.patient-list {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
padding: 5px 15px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.patient-card {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
margin-bottom: 1px; // Separator line
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.checkbox-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-row-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.patient-info {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
margin-right: 10px;
|
||||
|
||||
.patient-name {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.patient-meta {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.patient-tags {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
|
||||
.tag {
|
||||
font-size: 10px;
|
||||
color: #5d8aff;
|
||||
border: 1px solid #5d8aff;
|
||||
padding: 0 4px;
|
||||
border-radius: 8px;
|
||||
height: 16px;
|
||||
line-height: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-row-bottom {
|
||||
font-size: 14px;
|
||||
|
||||
.record-text {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.no-record {
|
||||
color: #bdc3c7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.batch-footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 50px;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px;
|
||||
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||||
z-index: 99;
|
||||
|
||||
.left-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.footer-text {
|
||||
margin-left: 5px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.right-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
.footer-btn {
|
||||
font-size: 14px;
|
||||
padding: 0 15px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
&.plain {
|
||||
border: 1px solid #ddd;
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: #5d8aff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-index {
|
||||
width: 20px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 20px;
|
||||
bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
z-index: 10;
|
||||
|
||||
.index-item {
|
||||
font-size: 10px;
|
||||
color: #555;
|
||||
padding: 2px 0;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
239
pages/case/group-manage.vue
Normal file
239
pages/case/group-manage.vue
Normal file
@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<view class="manage-container">
|
||||
<view class="group-list">
|
||||
<view v-for="(item, index) in groups" :key="index" class="group-item">
|
||||
<view class="left-action" @click="handleDelete(index)">
|
||||
<uni-icons type="minus-filled" size="24" color="#ff4d4f"></uni-icons>
|
||||
</view>
|
||||
<view class="group-name">{{ item.name }}</view>
|
||||
<view class="right-actions">
|
||||
<uni-icons type="compose" size="24" color="#5d8aff" class="icon-edit" @click="handleEdit(item, index)"></uni-icons>
|
||||
<uni-icons type="bars" size="24" color="#5d8aff" class="icon-drag"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Bottom Button -->
|
||||
<view class="footer">
|
||||
<button class="add-btn" @click="handleAdd">添加新分组</button>
|
||||
</view>
|
||||
|
||||
<!-- Dialog -->
|
||||
<view v-if="showDialog" class="dialog-mask">
|
||||
<view class="dialog-content">
|
||||
<view class="dialog-header">{{ dialogTitle }}</view>
|
||||
<view class="dialog-body">
|
||||
<input class="dialog-input" type="text" v-model="inputValue" placeholder="请输入分组名称" />
|
||||
</view>
|
||||
<view class="dialog-footer">
|
||||
<button class="dialog-btn cancel" @click="closeDialog">取消</button>
|
||||
<button class="dialog-btn confirm" @click="handleSave">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
// State
|
||||
const groups = ref([
|
||||
{ id: 1, name: '糖尿病' },
|
||||
{ id: 2, name: '高血压' },
|
||||
{ id: 3, name: '高血脂' }
|
||||
]);
|
||||
|
||||
const showDialog = ref(false);
|
||||
const dialogMode = ref('add'); // 'add' or 'edit'
|
||||
const inputValue = ref('');
|
||||
const editingIndex = ref(-1);
|
||||
|
||||
const dialogTitle = ref('添加新分组');
|
||||
|
||||
// Methods
|
||||
const handleAdd = () => {
|
||||
dialogMode.value = 'add';
|
||||
dialogTitle.value = '添加新分组';
|
||||
inputValue.value = '';
|
||||
showDialog.value = true;
|
||||
};
|
||||
|
||||
const handleEdit = (item, index) => {
|
||||
dialogMode.value = 'edit';
|
||||
dialogTitle.value = '编辑分组名称';
|
||||
inputValue.value = item.name;
|
||||
editingIndex.value = index;
|
||||
showDialog.value = true;
|
||||
};
|
||||
|
||||
const handleDelete = (index) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该分组吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
groups.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
showDialog.value = false;
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (!inputValue.value.trim()) {
|
||||
uni.showToast({ title: '请输入分组名称', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogMode.value === 'add') {
|
||||
groups.value.push({
|
||||
id: Date.now(),
|
||||
name: inputValue.value
|
||||
});
|
||||
} else {
|
||||
groups.value[editingIndex.value].name = inputValue.value;
|
||||
}
|
||||
|
||||
closeDialog();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.manage-container {
|
||||
min-height: 100vh;
|
||||
background-color: #fff;
|
||||
padding-bottom: 80px; // Space for footer
|
||||
}
|
||||
|
||||
.group-list {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.group-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
.left-action {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.right-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
|
||||
.icon-edit, .icon-drag {
|
||||
padding: 5px; // Increase tap area
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
padding: 15px 20px 30px; // Safe area padding
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.add-btn {
|
||||
background-color: #5d8aff;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
border: none;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Dialog Styles
|
||||
.dialog-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
width: 280px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
|
||||
.dialog-header {
|
||||
padding: 20px 0 10px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
padding: 10px 20px 20px;
|
||||
|
||||
.dialog-input {
|
||||
border: 1px solid #ddd;
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
padding: 0 20px 20px;
|
||||
justify-content: space-between;
|
||||
gap: 15px;
|
||||
|
||||
.dialog-btn {
|
||||
flex: 1;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
|
||||
&.cancel {
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
&.confirm {
|
||||
background-color: #5d8aff;
|
||||
color: #fff;
|
||||
border: none; // Remove border for confirm button usually
|
||||
}
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
132
pages/case/patient-create.vue
Normal file
132
pages/case/patient-create.vue
Normal file
@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="body">
|
||||
<scroll-view scroll-y class="scroll">
|
||||
<view class="form-wrap">
|
||||
<form-template ref="formRef" :items="baseItems" :form="form" :rule="rules" @change="onChange" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
|
||||
<view class="footer">
|
||||
<button class="primary" @click="next">下一步</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import FormTemplate from '@/components/form-template/index.vue';
|
||||
import validate from '@/utils/validate';
|
||||
|
||||
const STORAGE_KEY = 'patient-create-base';
|
||||
|
||||
const formRef = ref(null);
|
||||
const form = reactive({
|
||||
name: '',
|
||||
gender: '',
|
||||
age: '',
|
||||
mobile: '',
|
||||
birthday: '',
|
||||
idType: '',
|
||||
idNo: ''
|
||||
});
|
||||
|
||||
const baseItems = [
|
||||
{ title: 'name', name: '姓名', type: 'input', operateType: 'formCell', required: true, wordLimit: 20, inputType: 'text' },
|
||||
{ title: 'gender', name: '性别', type: 'select', operateType: 'formCell', required: false, range: ['男', '女'] },
|
||||
{ title: 'age', name: '年龄', type: 'input', operateType: 'formCell', required: false, wordLimit: 3, inputType: 'number' },
|
||||
{ title: 'mobile', name: '手机号', type: 'input', operateType: 'formCell', required: false, wordLimit: 11, inputType: 'number' },
|
||||
{ title: 'birthday', name: '出生日期', type: 'date', operateType: 'formCell', required: false },
|
||||
{ title: 'idType', name: '证件类型', type: 'select', operateType: 'formCell', required: false, range: ['身份证', '护照', '港澳台通行证', '其他'] },
|
||||
{ title: 'idNo', name: '证件号', type: 'input', operateType: 'formCell', required: false, wordLimit: 30, inputType: 'text' }
|
||||
];
|
||||
|
||||
const rules = {
|
||||
idNo(value) {
|
||||
if (!value) return true;
|
||||
if (form.idType === '身份证') {
|
||||
const [ok, msg] = validate.isChinaId(value);
|
||||
if (!ok) return msg || '证件号格式不正确';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
onLoad(() => {
|
||||
const cached = uni.getStorageSync(STORAGE_KEY);
|
||||
if (cached && typeof cached === 'object') {
|
||||
Object.assign(form, cached);
|
||||
}
|
||||
});
|
||||
|
||||
function onChange({ title, value }) {
|
||||
form[title] = value;
|
||||
}
|
||||
|
||||
function next() {
|
||||
if (!formRef.value?.verify?.()) return;
|
||||
uni.setStorageSync(STORAGE_KEY, { ...form });
|
||||
uni.navigateTo({ url: '/pages/case/patient-inner-info' });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
height: 100vh;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
.body {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scroll {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-wrap {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.step-note {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: 92px;
|
||||
transform: translateX(-50%);
|
||||
background: #f2df52;
|
||||
color: #000;
|
||||
font-size: 14px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
|
||||
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.primary {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
background: #5d8aff;
|
||||
color: #fff;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.primary::after {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
144
pages/case/patient-inner-info.vue
Normal file
144
pages/case/patient-inner-info.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="body">
|
||||
<scroll-view scroll-y class="scroll">
|
||||
<view class="form-wrap">
|
||||
<form-template v-if="items.length" ref="formRef" :items="items" :form="form" :rule="rules" @change="onChange" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="footer">
|
||||
<button class="btn plain" @click="cancel">取消</button>
|
||||
<button class="btn primary" @click="save">完成</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import FormTemplate from '@/components/form-template/index.vue';
|
||||
|
||||
const BASE_KEY = 'patient-create-base';
|
||||
const INNER_KEY = 'patient-create-inner';
|
||||
|
||||
const formRef = ref(null);
|
||||
|
||||
const form = reactive({
|
||||
customerSource: '',
|
||||
firstVisitDate: '',
|
||||
diseaseTag: '',
|
||||
remark: ''
|
||||
});
|
||||
|
||||
const items = [
|
||||
{ title: 'customerSource', name: '患者来源', type: 'select', operateType: 'formCell', required: false, range: ['线上咨询', '同事推荐', '客户推荐', '其他'] },
|
||||
{ title: 'firstVisitDate', name: '首次就诊日期', type: 'date', operateType: 'formCell', required: false },
|
||||
{ title: 'diseaseTag', name: '慢病标签', type: 'select', operateType: 'formCell', required: false, range: ['糖尿病', '高血压', '冠心病', '慢阻肺'] },
|
||||
{ title: 'remark', name: '备注', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 }
|
||||
];
|
||||
|
||||
const rules = {};
|
||||
|
||||
onLoad(() => {
|
||||
const base = uni.getStorageSync(BASE_KEY);
|
||||
if (!base || typeof base !== 'object') {
|
||||
uni.showToast({ title: '请先填写基础信息', icon: 'none' });
|
||||
uni.navigateBack();
|
||||
return;
|
||||
}
|
||||
|
||||
const cached = uni.getStorageSync(INNER_KEY);
|
||||
if (cached && typeof cached === 'object') {
|
||||
Object.assign(form, cached);
|
||||
}
|
||||
});
|
||||
|
||||
function onChange({ title, value }) {
|
||||
form[title] = value;
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
|
||||
function save() {
|
||||
if (formRef.value?.verify && !formRef.value.verify()) return;
|
||||
|
||||
const base = uni.getStorageSync(BASE_KEY) || {};
|
||||
const payload = { ...base, ...form };
|
||||
|
||||
uni.setStorageSync(INNER_KEY, { ...form });
|
||||
|
||||
// 这里后续对接真实新增接口:payload 即最终提交数据
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '已完成新增(mock)。后续将对接真实建档接口。',
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
uni.removeStorageSync(BASE_KEY);
|
||||
uni.removeStorageSync(INNER_KEY);
|
||||
// 返回到病例列表
|
||||
uni.navigateBack({ delta: 2 });
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
height: 100vh;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
.body {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scroll {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
.form-wrap {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn.plain {
|
||||
background: #fff;
|
||||
color: #666;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
background: #5d8aff;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
225
pages/case/patient-invite.vue
Normal file
225
pages/case/patient-invite.vue
Normal file
@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="content">
|
||||
<view class="code-row">
|
||||
<text class="code-text">{{ displayTeamCode }}</text>
|
||||
<view class="help" @click="showHelp">
|
||||
<uni-icons type="help-filled" size="18" color="#5d8aff"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="qr-card">
|
||||
<image class="qr-image" :src="qrImageUrl" mode="aspectFit" @click="previewQr" />
|
||||
</view>
|
||||
|
||||
<view class="tips">
|
||||
<text class="tips-title">微信扫一扫上面的二维码</text>
|
||||
<text class="tips-sub">进入团队首页,即可发起线上咨询、建档授权等服务</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<button class="footer-btn outline" @click="saveQr">保存图片</button>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="footer-btn primary" open-type="share">分享到微信</button>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<button class="footer-btn primary" @click="shareFallback">分享到微信</button>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { onLoad, onShareAppMessage } from '@dcloudio/uni-app';
|
||||
|
||||
const teamCode = ref('133****3356');
|
||||
const qrImageUrl = ref('');
|
||||
|
||||
const displayTeamCode = computed(() => `${teamCode.value}服务团队码`);
|
||||
|
||||
const buildQrUrl = (text) => {
|
||||
// 仅用于演示UI:实际项目建议由后端返回可扫码的二维码图片地址
|
||||
const encoded = encodeURIComponent(text);
|
||||
return `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encoded}`;
|
||||
};
|
||||
|
||||
onLoad((query) => {
|
||||
if (query?.teamCode) teamCode.value = String(query.teamCode);
|
||||
if (query?.qrUrl) {
|
||||
qrImageUrl.value = String(query.qrUrl);
|
||||
} else {
|
||||
qrImageUrl.value = buildQrUrl(`TEAM_CODE:${teamCode.value}`);
|
||||
}
|
||||
});
|
||||
|
||||
const showHelp = () => {
|
||||
uni.showModal({
|
||||
title: '服务团队码',
|
||||
content: '患者微信扫一扫二维码进入团队首页,可发起线上咨询、建档授权等服务。',
|
||||
showCancel: false
|
||||
});
|
||||
};
|
||||
|
||||
const previewQr = () => {
|
||||
if (!qrImageUrl.value) return;
|
||||
uni.previewImage({ urls: [qrImageUrl.value] });
|
||||
};
|
||||
|
||||
const saveQr = () => {
|
||||
if (!qrImageUrl.value) {
|
||||
uni.showToast({ title: '二维码生成中,请稍后', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '保存中...' });
|
||||
uni.downloadFile({
|
||||
url: qrImageUrl.value,
|
||||
success: (res) => {
|
||||
const filePath = res.tempFilePath;
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath,
|
||||
success: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '已保存到相册', icon: 'success' });
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showModal({
|
||||
title: '保存失败',
|
||||
content: '请在系统设置中允许保存到相册后重试。',
|
||||
confirmText: '去设置',
|
||||
cancelText: '取消',
|
||||
success: (r) => {
|
||||
if (r.confirm) {
|
||||
uni.openSetting?.({});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '下载二维码失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const shareFallback = () => {
|
||||
uni.showToast({ title: '请使用右上角菜单分享', icon: 'none' });
|
||||
};
|
||||
|
||||
onShareAppMessage(() => {
|
||||
return {
|
||||
title: '邀请您加入服务团队',
|
||||
path: `/pages/case/patient-invite?teamCode=${encodeURIComponent(teamCode.value)}`
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #ffffff;
|
||||
padding-bottom: calc(76px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 22px 16px 0;
|
||||
}
|
||||
|
||||
.code-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.code-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.help {
|
||||
margin-left: 8px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qr-card {
|
||||
margin: 18px auto 0;
|
||||
width: 280px;
|
||||
height: 280px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qr-image {
|
||||
width: 260px;
|
||||
height: 260px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.tips-title {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #3a3a3a;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.tips-sub {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.footer-btn {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.footer-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.footer-btn.outline {
|
||||
background: #fff;
|
||||
color: #5d8aff;
|
||||
border: 1px solid #5d8aff;
|
||||
}
|
||||
|
||||
.footer-btn.primary {
|
||||
background: #5d8aff;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
283
pages/case/search.vue
Normal file
283
pages/case/search.vue
Normal file
@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<view class="search-container">
|
||||
<!-- Search Header -->
|
||||
<view class="search-header">
|
||||
<view class="search-bar">
|
||||
<uni-icons type="search" size="18" color="#999" class="search-icon"></uni-icons>
|
||||
<input
|
||||
class="search-input"
|
||||
placeholder="搜索患者名称/手机号/院内ID号"
|
||||
v-model="searchQuery"
|
||||
confirm-type="search"
|
||||
focus
|
||||
@input="handleSearch"
|
||||
/>
|
||||
<uni-icons v-if="searchQuery" type="clear" size="18" color="#ccc" @click="clearSearch" class="clear-icon"></uni-icons>
|
||||
</view>
|
||||
<text class="cancel-btn" @click="goBack">取消</text>
|
||||
</view>
|
||||
|
||||
<!-- Search Results -->
|
||||
<scroll-view v-if="searchQuery" scroll-y class="search-results">
|
||||
<view v-if="searchResults.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无搜索结果</text>
|
||||
</view>
|
||||
|
||||
<view v-else>
|
||||
<view v-for="(patient, index) in searchResults" :key="index" class="patient-card">
|
||||
<!-- Row 1 -->
|
||||
<view class="card-row-top">
|
||||
<view class="patient-info">
|
||||
<text class="patient-name">{{ patient.name }}</text>
|
||||
<text class="patient-meta">{{ patient.gender }}/{{ patient.age }}岁</text>
|
||||
</view>
|
||||
<view class="patient-tags">
|
||||
<view v-for="(tag, tIndex) in patient.tags" :key="tIndex" class="tag">
|
||||
{{ tag }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Row 2 -->
|
||||
<view class="card-row-bottom">
|
||||
<text v-if="patient.record" class="record-text">
|
||||
{{ patient.record.type }} / {{ patient.record.date }} / {{ patient.record.diagnosis }}
|
||||
</text>
|
||||
<text v-else class="no-record">暂无病历记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- History or Suggestions (when no search) -->
|
||||
<view v-else class="search-tips">
|
||||
<text class="tips-text">输入患者名称、手机号或院内ID号进行搜索</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
// State
|
||||
const searchQuery = ref('');
|
||||
|
||||
// Mock all patients data (same as case.vue)
|
||||
const allPatients = [
|
||||
{
|
||||
letter: 'A',
|
||||
data: [
|
||||
{
|
||||
name: '安乐', gender: '男', age: 45, tags: ['糖尿病'],
|
||||
record: { type: '门诊', date: '2026.1.10', diagnosis: '2型糖尿病' },
|
||||
createTime: '2026.1.19 14:30', creator: '李医生', phone: '13888888888', hospitalId: '1001'
|
||||
},
|
||||
{
|
||||
name: '奥利奥', gender: '女', age: 22, tags: [], record: null,
|
||||
createTime: '2026.1.15 09:00', creator: '王医生', phone: '13999999999', hospitalId: '1002'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
letter: 'L',
|
||||
data: [
|
||||
{
|
||||
name: '李珊珊', gender: '女', age: 37, tags: ['糖尿病', '高血压'],
|
||||
record: { type: '门诊', date: '2026.1.10', diagnosis: '急性上呼吸道感染' },
|
||||
createTime: '2026.1.10 10:20', creator: '张医生', phone: '13666666666', hospitalId: '1003'
|
||||
},
|
||||
{
|
||||
name: '李珊珊', gender: '女', age: 37, tags: [],
|
||||
record: { type: '住院', date: '2026.1.10', diagnosis: '急性上呼吸道感染' },
|
||||
createTime: '2025.12.30 11:00', creator: '张医生', phone: '13666666666', hospitalId: '1003'
|
||||
},
|
||||
{
|
||||
name: '李某某', gender: '女', age: 37, tags: [], record: null,
|
||||
createTime: '2025.12.01 08:30', creator: '系统导入', phone: '13555555555', hospitalId: '1004'
|
||||
},
|
||||
{
|
||||
name: '李四', gender: '男', age: 50, tags: ['高血压'], record: null,
|
||||
createTime: '2026.1.18 16:45', creator: '管理员', phone: '13444444444', hospitalId: '1005'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
letter: 'Z',
|
||||
data: [
|
||||
{
|
||||
name: '张三', gender: '男', age: 28, tags: [], record: null,
|
||||
createTime: '2026.1.19 10:00', creator: '赵医生', phone: '13333333333', hospitalId: '1006'
|
||||
},
|
||||
{
|
||||
name: '张敏', gender: '女', age: 32, tags: ['高血压'],
|
||||
record: { type: '门诊', date: '2025.12.15', diagnosis: '高血压' },
|
||||
createTime: '2025.11.20 15:15', creator: '孙医生', phone: '13222222222', hospitalId: '1007'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Computed
|
||||
const searchResults = computed(() => {
|
||||
if (!searchQuery.value) return [];
|
||||
|
||||
const q = searchQuery.value.toLowerCase();
|
||||
let results = [];
|
||||
|
||||
allPatients.forEach(group => {
|
||||
group.data.forEach(p => {
|
||||
if (p.name.includes(q) ||
|
||||
(p.phone && p.phone.includes(q)) ||
|
||||
(p.hospitalId && p.hospitalId.includes(q))) {
|
||||
results.push(p);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return results;
|
||||
});
|
||||
|
||||
// Methods
|
||||
const handleSearch = () => {
|
||||
// Search logic handled by computed property
|
||||
};
|
||||
|
||||
const clearSearch = () => {
|
||||
searchQuery.value = '';
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
|
||||
.search-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.search-bar {
|
||||
flex: 1;
|
||||
height: 36px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
margin-right: 10px;
|
||||
|
||||
.search-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
font-size: 14px;
|
||||
color: #5d8aff;
|
||||
}
|
||||
}
|
||||
|
||||
.search-results {
|
||||
flex: 1;
|
||||
|
||||
.empty-state {
|
||||
padding: 80px 20px;
|
||||
text-align: center;
|
||||
|
||||
.empty-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-tips {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
|
||||
.tips-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.patient-card {
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
margin-bottom: 1px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.card-row-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.patient-info {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
margin-right: 10px;
|
||||
|
||||
.patient-name {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.patient-meta {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.patient-tags {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
|
||||
.tag {
|
||||
font-size: 10px;
|
||||
color: #5d8aff;
|
||||
border: 1px solid #5d8aff;
|
||||
padding: 0 4px;
|
||||
border-radius: 8px;
|
||||
height: 16px;
|
||||
line-height: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-row-bottom {
|
||||
font-size: 14px;
|
||||
|
||||
.record-text {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.no-record {
|
||||
color: #bdc3c7;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user