ykt-wxapp/pages/case/search.vue

353 lines
8.8 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="搜索患者名称/手机号/院内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>