353 lines
8.8 KiB
Vue
353 lines
8.8 KiB
Vue
<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="searching && searchResults.length === 0" class="empty-state">
|
||
<text class="empty-text">搜索中...</text>
|
||
</view>
|
||
|
||
<view v-else-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" @click="goDetail(patient)">
|
||
<!-- 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';
|
||
import { storeToRefs } from 'pinia';
|
||
|
||
import api from '@/utils/api';
|
||
import useAccountStore from '@/store/account';
|
||
import useDebounce from '@/utils/useDebounce';
|
||
import { toast } from '@/utils/widget';
|
||
|
||
// State
|
||
const searchQuery = ref('');
|
||
const searchResultsRaw = ref([]);
|
||
const searching = ref(false);
|
||
|
||
const CURRENT_TEAM_STORAGE_KEY = 'ykt_case_current_team';
|
||
const DETAIL_STORAGE_KEY = 'ykt_case_archive_detail';
|
||
|
||
const accountStore = useAccountStore();
|
||
const { account, doctorInfo } = storeToRefs(accountStore);
|
||
|
||
function asArray(value) {
|
||
return Array.isArray(value) ? value : [];
|
||
}
|
||
|
||
function getUserId() {
|
||
const d = doctorInfo.value || {};
|
||
const a = account.value || {};
|
||
return String(d.userid || d.userId || d.corpUserId || a.userid || a.userId || '') || '';
|
||
}
|
||
|
||
function getTeamContext() {
|
||
const cached = uni.getStorageSync(CURRENT_TEAM_STORAGE_KEY) || {};
|
||
const corpId = String(cached.corpId || account.value?.corpId || '') || '';
|
||
const teamId = String(cached.teamId || '') || '';
|
||
return { corpId, teamId };
|
||
}
|
||
|
||
function formatPatient(raw) {
|
||
// 优先使用后端返回的 tagNames(标签名称数组)
|
||
const rawTagNames = asArray(raw?.tagNames).filter((i) => typeof i === 'string' && i.trim());
|
||
// 其次使用 tags(如果是字符串数组)
|
||
const rawTags = asArray(raw?.tags).filter((i) => typeof i === 'string' && i.trim());
|
||
// 最后才使用 tagIds(仅作为兜底,不推荐显示)
|
||
const tagIds = asArray(raw?.tagIds).map(String).filter(Boolean);
|
||
|
||
// 解析标签:优先 tagNames > tags(字符串) > tagIds
|
||
const displayTags = rawTagNames.length ? rawTagNames : (rawTags.length ? rawTags : []);
|
||
|
||
const mobiles = asArray(raw?.mobiles).map(String).filter(Boolean);
|
||
const mobile = raw?.mobile ? String(raw.mobile) : (mobiles[0] || '');
|
||
|
||
// 解析病历信息
|
||
let record = null;
|
||
if (raw?.latestRecord && typeof raw.latestRecord === 'object') {
|
||
const lr = raw.latestRecord;
|
||
const type = lr.type || '';
|
||
const date = lr.date || '';
|
||
const diagnosis = lr.diagnosis || '';
|
||
if (type || date || diagnosis) {
|
||
record = {
|
||
type: type || '-',
|
||
date: date || '-',
|
||
diagnosis: diagnosis || '-'
|
||
};
|
||
}
|
||
}
|
||
|
||
return {
|
||
...raw,
|
||
_id: raw?._id || raw?.id || '',
|
||
name: raw?.name || raw?.customerName || '',
|
||
gender: raw?.sex || raw?.gender || '',
|
||
age: raw?.age ?? '',
|
||
tags: displayTags,
|
||
mobiles,
|
||
mobile,
|
||
record,
|
||
createTime: raw?.createTime || '',
|
||
creator: raw?.creatorName || raw?.creator || '',
|
||
hospitalId: raw?.customerNumber || raw?.hospitalId || '',
|
||
};
|
||
}
|
||
|
||
// Computed
|
||
const searchResults = computed(() => {
|
||
return searchResultsRaw.value || [];
|
||
});
|
||
|
||
// Methods
|
||
const doSearch = useDebounce(async () => {
|
||
const q = String(searchQuery.value || '').trim();
|
||
if (!q) {
|
||
searchResultsRaw.value = [];
|
||
return;
|
||
}
|
||
|
||
const userId = getUserId();
|
||
const { corpId, teamId } = getTeamContext();
|
||
if (!corpId || !teamId || !userId) {
|
||
toast('缺少用户/团队信息,请先完成登录与个人信息');
|
||
return;
|
||
}
|
||
|
||
searching.value = true;
|
||
const res = await api('searchCorpCustomerForCaseList', {
|
||
corpId,
|
||
userId,
|
||
teamId,
|
||
name: q,
|
||
page: 1,
|
||
pageSize: 50,
|
||
});
|
||
searching.value = false;
|
||
|
||
if (!res?.success) {
|
||
toast(res?.message || '搜索失败');
|
||
return;
|
||
}
|
||
const payload =
|
||
res && typeof res === 'object'
|
||
? res.data && typeof res.data === 'object' && !Array.isArray(res.data)
|
||
? res.data
|
||
: res
|
||
: {};
|
||
const list = Array.isArray(payload.list) ? payload.list : Array.isArray(payload.data) ? payload.data : [];
|
||
searchResultsRaw.value = list.map(formatPatient);
|
||
}, 600);
|
||
|
||
const handleSearch = () => doSearch();
|
||
|
||
const clearSearch = () => {
|
||
searchQuery.value = '';
|
||
searchResultsRaw.value = [];
|
||
};
|
||
|
||
const goBack = () => {
|
||
uni.navigateBack();
|
||
};
|
||
|
||
const goDetail = (patient) => {
|
||
const id = patient?._id || patient?.id || patient?.mobile || patient?.phone || '';
|
||
uni.setStorageSync(DETAIL_STORAGE_KEY, {
|
||
_id: id,
|
||
name: patient.name,
|
||
sex: patient.gender,
|
||
age: patient.age,
|
||
mobile: patient.mobile,
|
||
mobiles: Array.isArray(patient.mobiles) ? patient.mobiles : (patient.mobile ? [patient.mobile] : []),
|
||
createTime: patient.createTime,
|
||
creator: patient.creator,
|
||
});
|
||
uni.navigateTo({ url: `/pages/case/archive-detail?id=${encodeURIComponent(String(id))}` });
|
||
};
|
||
</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;
|
||
display: block;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.no-record {
|
||
color: #bdc3c7;
|
||
}
|
||
}
|
||
}
|
||
</style>
|