ykt-wxapp/pages/case/search.vue

395 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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="搜索患者名称/手机号/病案号"
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">输入患者名称手机号或病案号进行搜索</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 searchSeq = ref(0);
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] || '');
const customerNumber = raw?.customerNumber || raw?.hospitalId || '';
const customerProfileNo2 = raw?.customerProfileNo2 || '';
const customerProfileNo3 = raw?.customerProfileNo3 || '';
function normalizeRecordTypeLabel(value) {
const s = String(value || '').trim();
if (!s) return '';
const lower = s.toLowerCase();
const base = lower.endsWith('record') && s.length > 6 ? s.slice(0, -6) : s;
const baseLower = String(base).toLowerCase();
if (baseLower === 'outpatient' || baseLower === 'out_patient' || baseLower === 'out-patient') return '门诊记录';
if (baseLower === 'inhospital' || baseLower === 'in_hospital' || baseLower === 'in-hospital' || baseLower === 'inpatient') return '住院记录';
if (baseLower === 'preconsultation' || baseLower === 'pre_consultation' || baseLower === 'pre-consultation' || baseLower === 'preconsultationtemplate') return '预问诊记录';
if (baseLower === 'physicalexaminationtemplate' || baseLower === 'physicalexamination' || baseLower === 'physical_examination') return '体检记录';
// 如果已经是中文(或后端已给展示名),直接返回
return s;
}
// 解析病历信息
let record = null;
if (raw?.latestRecord && typeof raw.latestRecord === 'object') {
const lr = raw.latestRecord;
const type = normalizeRecordTypeLabel(lr.type || lr.medicalTypeName || lr.medicalType || '');
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: customerNumber,
customerNumber,
customerProfileNo2,
customerProfileNo3,
};
}
function normalizeText(value) {
return String(value || '').trim();
}
function normalizeDigits(value) {
const s = normalizeText(value);
return s.replace(/\D/g, '');
}
async function fetchList(params) {
const res = await api('searchCorpCustomerForCaseList', params);
if (!res?.success) {
throw new Error(res?.message || '搜索失败');
}
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 : [];
return list.map(formatPatient);
}
// 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;
}
const seq = (searchSeq.value += 1);
searching.value = true;
try {
const list = await fetchList({
corpId,
userId,
teamId,
keyword: q,
page: 1,
pageSize: 50,
});
if (seq !== searchSeq.value) return;
searchResultsRaw.value = list;
} catch (e) {
if (seq !== searchSeq.value) return;
toast(e?.message || '搜索失败');
} finally {
if (seq === searchSeq.value) searching.value = false;
}
}, 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: #0877F1;
}
}
.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: #0877F1;
border: 1px solid #0877F1;
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>