feat:接入病历接口
This commit is contained in:
parent
8e72ec6d24
commit
2fc385f994
@ -1,4 +1,4 @@
|
|||||||
MP_API_BASE_URL=http://localhost:8080
|
MP_API_BASE_URL=http://192.168.137.1:8080
|
||||||
MP_CACHE_PREFIX=development
|
MP_CACHE_PREFIX=development
|
||||||
MP_WX_APP_ID=wx93af55767423938e
|
MP_WX_APP_ID=wx93af55767423938e
|
||||||
MP_CORP_ID=wwe3fb2faa52cf9dfb
|
MP_CORP_ID=wwe3fb2faa52cf9dfb
|
||||||
|
|||||||
@ -30,13 +30,9 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="record-body">
|
<view class="record-body">
|
||||||
<view class="line">
|
<view v-for="(l, idx) in getDisplayLines(r)" :key="`${r._id}_${idx}`" class="line">
|
||||||
<text class="line-label">诊断:</text>
|
<text class="line-label">{{ l.label }}</text>
|
||||||
<text class="line-value">{{ getDiagnosis(r) }}</text>
|
<text class="line-value">{{ l.value }}</text>
|
||||||
</view>
|
|
||||||
<view v-if="r.templateType === 'inhospital' && r.surgeryName" class="line">
|
|
||||||
<text class="line-label">手术:</text>
|
|
||||||
<text class="line-value">{{ r.surgeryName }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="getFiles(r).length" class="thumbs">
|
<view v-if="getFiles(r).length" class="thumbs">
|
||||||
@ -47,8 +43,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="record-foot">
|
<view class="record-foot">
|
||||||
<view class="foot-left">创建时间:{{ r.createDateStr || '' }}</view>
|
<view class="foot-left">{{ getCreateFooter(r) }}</view>
|
||||||
<view class="foot-right">创建人:{{ r.creatorName || '—' }}</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="records.length === 0" class="empty">暂无数据</view>
|
<view v-if="records.length === 0" class="empty">暂无数据</view>
|
||||||
@ -61,8 +56,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
import { ensureSeed, getCurrentTeamId, getVisitRecordTemplates, queryVisitRecords } from './mock';
|
import dayjs from 'dayjs';
|
||||||
|
import { VISIT_RECORD_TEMPLATES } from './templates';
|
||||||
|
import api from '@/utils/api';
|
||||||
|
import { loading, hideLoading } from '@/utils/widget';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: { type: Object, default: () => ({}) },
|
data: { type: Object, default: () => ({}) },
|
||||||
@ -70,7 +68,7 @@ const props = defineProps({
|
|||||||
floatingBottom: { type: Number, default: 16 },
|
floatingBottom: { type: Number, default: 16 },
|
||||||
});
|
});
|
||||||
|
|
||||||
const templates = ref(getVisitRecordTemplates());
|
const templates = ref(VISIT_RECORD_TEMPLATES.map(t => ({ name: t.templateName, templateType: t.templateType })));
|
||||||
|
|
||||||
const typeRange = computed(() => [{ name: '全部', value: 'ALL' }, ...templates.value.map((t) => ({ name: t.name, value: t.templateType }))]);
|
const typeRange = computed(() => [{ name: '全部', value: 'ALL' }, ...templates.value.map((t) => ({ name: t.name, value: t.templateType }))]);
|
||||||
const currentType = ref({ name: '全部', value: 'ALL' });
|
const currentType = ref({ name: '全部', value: 'ALL' });
|
||||||
@ -85,6 +83,11 @@ const timeRangeOptions = [
|
|||||||
];
|
];
|
||||||
const currentTimeRange = ref(timeRangeOptions[0]);
|
const currentTimeRange = ref(timeRangeOptions[0]);
|
||||||
|
|
||||||
|
function getCurrentTeamId() {
|
||||||
|
const team = uni.getStorageSync('ykt_case_current_team') || {};
|
||||||
|
return team?.teamId ? String(team.teamId) : '';
|
||||||
|
}
|
||||||
|
|
||||||
const teamId = ref(getCurrentTeamId());
|
const teamId = ref(getCurrentTeamId());
|
||||||
const showShareTip = computed(() => {
|
const showShareTip = computed(() => {
|
||||||
if (props.data && typeof props.data.shareAllTeams === 'boolean') return props.data.shareAllTeams;
|
if (props.data && typeof props.data.shareAllTeams === 'boolean') return props.data.shareAllTeams;
|
||||||
@ -100,15 +103,162 @@ const shareAllTeamsForQuery = computed(() => {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
function refreshList() {
|
function getCorpId() {
|
||||||
|
const team = uni.getStorageSync('ykt_case_current_team') || {};
|
||||||
|
return team?.corpId ? String(team.corpId) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const userNameMap = ref({});
|
||||||
|
const loadedTeamId = ref('');
|
||||||
|
function resolveUserName(userId) {
|
||||||
|
const id = String(userId || '');
|
||||||
|
if (!id) return '';
|
||||||
|
const map = userNameMap.value || {};
|
||||||
|
return String(map[id] || '') || id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTeamMembers() {
|
||||||
|
const team = uni.getStorageSync('ykt_case_current_team') || {};
|
||||||
|
const teamId = team?.teamId ? String(team.teamId) : '';
|
||||||
|
const corpId = getCorpId();
|
||||||
|
if (!teamId || !corpId) return;
|
||||||
|
if (loadedTeamId.value === teamId && Object.keys(userNameMap.value || {}).length > 0) return;
|
||||||
|
loadedTeamId.value = teamId;
|
||||||
|
|
||||||
|
const res = await api('getTeamData', { corpId, teamId });
|
||||||
|
if (!res?.success) return;
|
||||||
|
const t = res?.data && typeof res.data === 'object' ? res.data : {};
|
||||||
|
const members = Array.isArray(t.memberList) ? t.memberList : [];
|
||||||
|
userNameMap.value = members.reduce((acc, m) => {
|
||||||
|
const uid = String(m?.userid || '');
|
||||||
|
if (!uid) return acc;
|
||||||
|
acc[uid] = String(m?.anotherName || m?.name || m?.userid || '') || uid;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSortTimeTitle(templateType) {
|
||||||
|
if (templateType === 'outpatient') return 'visitTime';
|
||||||
|
if (templateType === 'inhospital') return 'inhosDate';
|
||||||
|
if (templateType === 'preConsultation') return 'consultDate';
|
||||||
|
if (templateType === 'physicalExaminationTemplate') return 'inspectDate';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeText(v) {
|
||||||
|
if (Array.isArray(v)) return v.filter((i) => i !== null && i !== undefined && String(i).trim()).join(',');
|
||||||
|
if (v === 0) return '0';
|
||||||
|
return v ? String(v) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPositiveFind(v, { withOpinion = false } = {}) {
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
const list = v
|
||||||
|
.map((i) => (i && typeof i === 'object' ? { category: i.category, opinion: i.opinion } : null))
|
||||||
|
.filter((i) => i && (i.category || i.opinion));
|
||||||
|
if (!list.length) return '';
|
||||||
|
if (!withOpinion) return list.map((i) => String(i.category || '').trim()).filter(Boolean).join(':');
|
||||||
|
return list
|
||||||
|
.map((i) => {
|
||||||
|
const c = String(i.category || '').trim();
|
||||||
|
const o = String(i.opinion || '').trim();
|
||||||
|
if (c && o) return `${c}:${o}`;
|
||||||
|
return c || o;
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(';');
|
||||||
|
}
|
||||||
|
return normalizeText(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTemplateName(type) {
|
||||||
|
const t = VISIT_RECORD_TEMPLATES.find((i) => i && i.templateType === type);
|
||||||
|
return t?.templateName ? String(t.templateName) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDateStr(sortTime) {
|
||||||
|
if (!sortTime) return '';
|
||||||
|
const d = dayjs(sortTime);
|
||||||
|
return d.isValid() ? d.format('YYYY-MM-DD') : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDateTimeStr(ts) {
|
||||||
|
if (!ts) return '';
|
||||||
|
const d = dayjs(ts);
|
||||||
|
return d.isValid() ? d.format('YYYY-MM-DD HH:mm') : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshList() {
|
||||||
if (!props.archiveId) return;
|
if (!props.archiveId) return;
|
||||||
records.value = queryVisitRecords({
|
|
||||||
archiveId: props.archiveId,
|
const corpId = getCorpId();
|
||||||
medicalType: currentType.value.value,
|
if (!corpId) return;
|
||||||
timeRange: currentTimeRange.value.value,
|
loadTeamMembers();
|
||||||
teamId: teamId.value,
|
|
||||||
shareAllTeams: shareAllTeamsForQuery.value,
|
loading('加载中...');
|
||||||
|
try {
|
||||||
|
const params = { memberId: props.archiveId, corpId };
|
||||||
|
|
||||||
|
// 添加类型筛选
|
||||||
|
params.medicalType =
|
||||||
|
currentType.value.value === 'ALL' ? templates.value.map((i) => i.templateType) : currentType.value.value;
|
||||||
|
|
||||||
|
// 添加时间筛选
|
||||||
|
if (currentTimeRange.value.value !== 'ALL') {
|
||||||
|
const now = Date.now();
|
||||||
|
if (currentTimeRange.value.value === 'today') {
|
||||||
|
const todayStart = new Date(new Date().setHours(0, 0, 0, 0)).getTime();
|
||||||
|
params.startTime = todayStart;
|
||||||
|
params.endTime = now;
|
||||||
|
} else if (currentTimeRange.value.value === '7d') {
|
||||||
|
params.startTime = now - 7 * 24 * 60 * 60 * 1000;
|
||||||
|
params.endTime = now;
|
||||||
|
} else if (currentTimeRange.value.value === '30d') {
|
||||||
|
params.startTime = now - 30 * 24 * 60 * 60 * 1000;
|
||||||
|
params.endTime = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api('getCustomerMedicalRecord', params);
|
||||||
|
hideLoading();
|
||||||
|
|
||||||
|
// 接口返回结构兼容:
|
||||||
|
// - { success, message, list: [] }
|
||||||
|
// - { success, message, data: { list: [] } }
|
||||||
|
const list = Array.isArray(res?.list)
|
||||||
|
? res.list
|
||||||
|
: Array.isArray(res?.data?.list)
|
||||||
|
? res.data.list
|
||||||
|
: Array.isArray(res?.data)
|
||||||
|
? res.data
|
||||||
|
: [];
|
||||||
|
if (list.length) {
|
||||||
|
const mapped = list.map((r) => {
|
||||||
|
const t = String(r?.medicalType || r?.templateType || '') || '';
|
||||||
|
const timeTitle = getSortTimeTitle(t);
|
||||||
|
const dateStr = timeTitle ? normalizeText(r?.[timeTitle]) : '';
|
||||||
|
return {
|
||||||
|
...r,
|
||||||
|
templateType: t,
|
||||||
|
dateStr: dateStr || toDateStr(r?.sortTime),
|
||||||
|
createDateStr: r?.createTime ? dayjs(r.createTime).format('YYYY-MM-DD') : '',
|
||||||
|
createTimeStr: toDateTimeStr(r?.createTime),
|
||||||
|
tempName: r?.tempName || getTemplateName(t) || '病历',
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 未互通时,仅展示本团队数据
|
||||||
|
records.value = !shareAllTeamsForQuery.value && teamId.value
|
||||||
|
? mapped.filter((i) => String(i?.teamId || '') === String(teamId.value))
|
||||||
|
: mapped;
|
||||||
|
} else {
|
||||||
|
records.value = [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
hideLoading();
|
||||||
|
console.error('获取病历记录失败:', error);
|
||||||
|
records.value = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagClass = {
|
const tagClass = {
|
||||||
@ -120,8 +270,53 @@ const tagClass = {
|
|||||||
|
|
||||||
function getDiagnosis(r) {
|
function getDiagnosis(r) {
|
||||||
if (!r) return '--';
|
if (!r) return '--';
|
||||||
if (r.templateType === 'preConsultation') return r.chiefComplaint || r.summary || '--';
|
const t = r.templateType || r.medicalType;
|
||||||
return r.diagnosisName || r.summary || '--';
|
if (t === 'preConsultation') return normalizeText(r.chiefComplaint) || normalizeText(r.summary) || '--';
|
||||||
|
if (t === 'physicalExaminationTemplate') return formatPositiveFind(r.positiveFind) || normalizeText(r.summary) || '--';
|
||||||
|
if (t === 'outpatient' || t === 'inhospital') return normalizeText(r.diagnosisName || r.diagnosis) || normalizeText(r.summary) || '--';
|
||||||
|
return normalizeText(r.diagnosisName || r.diagnosis || r.summary) || '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstLine(v) {
|
||||||
|
const s = normalizeText(v);
|
||||||
|
return s || '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayLines(r) {
|
||||||
|
const t = r?.templateType || r?.medicalType;
|
||||||
|
if (t === 'outpatient') {
|
||||||
|
return [{ label: '门诊诊断:', value: firstLine(r.diagnosisName || r.diagnosis) }];
|
||||||
|
}
|
||||||
|
if (t === 'inhospital') {
|
||||||
|
const lines = [{ label: '入院诊断:', value: firstLine(r.diagnosisName || r.diagnosis) }];
|
||||||
|
const surgery = normalizeText(r.surgeryName);
|
||||||
|
if (surgery) lines.push({ label: '手术名称:', value: surgery });
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
if (t === 'physicalExaminationTemplate') {
|
||||||
|
return [{ label: '体检小结:', value: firstLine(r.summary) }];
|
||||||
|
}
|
||||||
|
if (t === 'preConsultation') {
|
||||||
|
const lines = [
|
||||||
|
{ label: '主诉:', value: firstLine(r.chiefComplaint) },
|
||||||
|
{ label: '现病史:', value: firstLine(r.presentIllness) },
|
||||||
|
];
|
||||||
|
const past = normalizeText(r.pastHistory);
|
||||||
|
if (past) lines.push({ label: '既往史:', value: past });
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
return [{ label: '摘要:', value: firstLine(r.summary) }];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCreateFooter(r) {
|
||||||
|
const time = r?.createTimeStr || r?.createDateStr || '';
|
||||||
|
const byCustomer = r?.ignore === 'checkIn';
|
||||||
|
if (byCustomer) return time ? `${time} 患者自建` : '患者自建';
|
||||||
|
|
||||||
|
const creatorId = String(r?.creator || '');
|
||||||
|
if (!creatorId) return time ? `创建时间:${time}` : '';
|
||||||
|
const name = resolveUserName(creatorId);
|
||||||
|
return time ? `${time} ${name}代建` : `${name}代建`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickType(e) {
|
function pickType(e) {
|
||||||
@ -157,13 +352,14 @@ function add() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function edit(record) {
|
function edit(record) {
|
||||||
|
const type = String(record?.medicalType || record?.templateType || '') || '';
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/case/visit-record-view?archiveId=${encodeURIComponent(props.archiveId)}&id=${encodeURIComponent(record._id)}`,
|
url: `/pages/case/visit-record-view?archiveId=${encodeURIComponent(props.archiveId)}&id=${encodeURIComponent(record._id)}&type=${encodeURIComponent(type)}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
ensureSeed(props.archiveId, props.data);
|
// archiveId 可能后置赋值:这里保留一次兜底刷新,主逻辑交给 watch
|
||||||
refreshList();
|
refreshList();
|
||||||
uni.$on('archive-detail:visit-record-changed', refreshList);
|
uni.$on('archive-detail:visit-record-changed', refreshList);
|
||||||
});
|
});
|
||||||
@ -171,6 +367,16 @@ onMounted(() => {
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
uni.$off('archive-detail:visit-record-changed', refreshList);
|
uni.$off('archive-detail:visit-record-changed', refreshList);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.archiveId,
|
||||||
|
(v, old) => {
|
||||||
|
const next = v ? String(v) : '';
|
||||||
|
const prev = old ? String(old) : '';
|
||||||
|
if (next && next !== prev) refreshList();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -265,9 +471,7 @@ onUnmounted(() => {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
display: -webkit-box;
|
white-space: nowrap;
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbs {
|
.thumbs {
|
||||||
@ -306,13 +510,6 @@ onUnmounted(() => {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
.foot-right {
|
|
||||||
flex: 1;
|
|
||||||
text-align: right;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-tag {
|
.record-tag {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@ -1,410 +0,0 @@
|
|||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
const DB_KEY = 'ykt_case_archive_detail_mockdb_v1';
|
|
||||||
|
|
||||||
export const VISIT_RECORD_TEMPLATES = [
|
|
||||||
{
|
|
||||||
templateType: 'outpatient',
|
|
||||||
templateName: '门诊记录',
|
|
||||||
templateList: [
|
|
||||||
{ title: 'visitTime', name: '就诊日期', type: 'date', operateType: 'formCell', required: true, format: 'YYYY-MM-DD' },
|
|
||||||
{ title: 'corpName', name: '就诊机构', type: 'input', operateType: 'formCell', required: false, wordLimit: 30, inputType: 'text' },
|
|
||||||
{ title: 'deptName', name: '科室', type: 'input', operateType: 'formCell', required: false, wordLimit: 30, inputType: 'text' },
|
|
||||||
{ title: 'doctor', name: '医生', type: 'input', operateType: 'formCell', required: false, wordLimit: 30, inputType: 'text' },
|
|
||||||
{ title: 'diagnosisName', name: '门诊诊断', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 },
|
|
||||||
{ title: 'treatmentPlan', name: '治疗方案', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 1000 },
|
|
||||||
{ title: 'disposePlan', name: '处置计划', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 1000 },
|
|
||||||
{ title: 'summary', name: '备注/摘要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 },
|
|
||||||
{ title: 'files', name: '文件上传', type: 'files', required: false },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
templateType: 'inhospital',
|
|
||||||
templateName: '住院记录',
|
|
||||||
templateList: [
|
|
||||||
{ title: 'inhosDate', name: '入院日期', type: 'date', operateType: 'formCell', required: true, format: 'YYYY-MM-DD' },
|
|
||||||
{ title: 'corpName', name: '住院机构', type: 'input', operateType: 'formCell', required: false, wordLimit: 30, inputType: 'text' },
|
|
||||||
{ title: 'diagnosisName', name: '入院诊断', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 },
|
|
||||||
{ title: 'surgeryName', name: '手术名称', type: 'input', operateType: 'formCell', required: false, wordLimit: 50, inputType: 'text' },
|
|
||||||
{ title: 'summary', name: '摘要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 },
|
|
||||||
{ title: 'files', name: '文件上传', type: 'files', required: false },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
templateType: 'preConsultation',
|
|
||||||
templateName: '预问诊记录',
|
|
||||||
templateList: [
|
|
||||||
{ title: 'consultDate', name: '问诊日期', type: 'date', operateType: 'formCell', required: true, format: 'YYYY-MM-DD' },
|
|
||||||
{ title: 'chiefComplaint', name: '主诉', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 300 },
|
|
||||||
{ title: 'presentIllness', name: '现病史', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 800 },
|
|
||||||
{ title: 'pastHistory', name: '既往史', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 800 },
|
|
||||||
{ title: 'allergyHistory', name: '过敏史', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 300 },
|
|
||||||
{ title: 'summary', name: '摘要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 },
|
|
||||||
{ title: 'files', name: '文件上传', type: 'files', required: false },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
templateType: 'physicalExaminationTemplate',
|
|
||||||
templateName: '体检记录',
|
|
||||||
templateList: [
|
|
||||||
{ title: 'inspectDate', name: '体检日期', type: 'date', operateType: 'formCell', required: true, format: 'YYYY-MM-DD' },
|
|
||||||
{ title: 'corpName', name: '体检机构', type: 'input', operateType: 'formCell', required: false, wordLimit: 30, inputType: 'text' },
|
|
||||||
{ title: 'inspectPakageName', name: '体检套餐', type: 'input', operateType: 'formCell', required: false, wordLimit: 50, inputType: 'text' },
|
|
||||||
{ title: 'positiveFind', name: '阳性发现', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 300 },
|
|
||||||
{ title: 'summary', name: '摘要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200 },
|
|
||||||
{ title: 'files', name: '文件上传', type: 'files', required: false },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function getVisitRecordTemplates() {
|
|
||||||
return VISIT_RECORD_TEMPLATES.map((i) => ({ templateType: i.templateType, name: i.templateName, templateList: i.templateList }));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getVisitRecordTemplate(templateType) {
|
|
||||||
return VISIT_RECORD_TEMPLATES.find((i) => i.templateType === templateType) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function safeParse(json) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(json);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDb() {
|
|
||||||
const raw = uni.getStorageSync(DB_KEY);
|
|
||||||
const db = raw && typeof raw === 'string' ? safeParse(raw) : raw;
|
|
||||||
const next = db && typeof db === 'object' ? db : {};
|
|
||||||
next.visitRecordsByArchiveId = next.visitRecordsByArchiveId || {};
|
|
||||||
next.serviceRecordsByArchiveId = next.serviceRecordsByArchiveId || {};
|
|
||||||
next.followupsByArchiveId = next.followupsByArchiveId || {};
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setDb(db) {
|
|
||||||
uni.setStorageSync(DB_KEY, JSON.stringify(db));
|
|
||||||
}
|
|
||||||
|
|
||||||
function uid(prefix) {
|
|
||||||
return `${prefix}_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ensureSeed(archiveId, archive) {
|
|
||||||
if (!archiveId) return;
|
|
||||||
const db = getDb();
|
|
||||||
|
|
||||||
if (!Array.isArray(db.visitRecordsByArchiveId[archiveId]) || db.visitRecordsByArchiveId[archiveId].length === 0) {
|
|
||||||
const now = Date.now();
|
|
||||||
db.visitRecordsByArchiveId[archiveId] = [
|
|
||||||
{
|
|
||||||
_id: uid('mr'),
|
|
||||||
medicalType: 'outpatient',
|
|
||||||
tempName: '门诊记录',
|
|
||||||
templateType: 'outpatient',
|
|
||||||
teamId: 'team_1',
|
|
||||||
sortTime: now - 1000 * 60 * 60 * 24 * 2,
|
|
||||||
visitTime: dayjs(now - 1000 * 60 * 60 * 24 * 2).format('YYYY-MM-DD'),
|
|
||||||
corpName: '某某医院',
|
|
||||||
deptName: '呼吸内科',
|
|
||||||
doctor: '李医生',
|
|
||||||
diagnosisName: '急性上呼吸道感染(mock)',
|
|
||||||
treatmentPlan: '建议:1)对症处理退热;2)多饮水休息;3)如出现呼吸困难及时就医。',
|
|
||||||
disposePlan: '建议:1)继续对症治疗;2)监测体温与血压;3)如3天无缓解或加重立即复诊。',
|
|
||||||
summary: '初诊:对症处理并随访。',
|
|
||||||
files: [{ url: '/static/tabbar/home.png', name: '示例图片1' }],
|
|
||||||
createTime: now - 1000 * 60 * 60 * 24 * 2,
|
|
||||||
creatorName: '李珊珊',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_id: uid('mr'),
|
|
||||||
medicalType: 'inhospital',
|
|
||||||
tempName: '住院记录',
|
|
||||||
templateType: 'inhospital',
|
|
||||||
teamId: 'team_2',
|
|
||||||
sortTime: now - 1000 * 60 * 60 * 24 * 15,
|
|
||||||
inhosDate: dayjs(now - 1000 * 60 * 60 * 24 * 15).format('YYYY-MM-DD'),
|
|
||||||
corpName: '某某医院',
|
|
||||||
diagnosisName: '术后复查(mock)',
|
|
||||||
surgeryName: '阑尾切除术',
|
|
||||||
summary: '复诊:术后复查,恢复良好。',
|
|
||||||
files: [],
|
|
||||||
createTime: now - 1000 * 60 * 60 * 24 * 15,
|
|
||||||
creatorName: '王护士',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_id: uid('mr'),
|
|
||||||
medicalType: 'preConsultation',
|
|
||||||
tempName: '预问诊记录',
|
|
||||||
templateType: 'preConsultation',
|
|
||||||
teamId: 'team_1',
|
|
||||||
sortTime: now - 1000 * 60 * 60 * 6,
|
|
||||||
consultDate: dayjs(now - 1000 * 60 * 60 * 6).format('YYYY-MM-DD'),
|
|
||||||
chiefComplaint: '咽痛、流涕 2 天(mock)',
|
|
||||||
presentIllness: '近2天受凉后出现咽痛、流涕,体温最高 38.2℃。',
|
|
||||||
pastHistory: '既往体健。',
|
|
||||||
allergyHistory: '无明确过敏史。',
|
|
||||||
summary: '建议对症处理,必要时线下就医。',
|
|
||||||
files: [],
|
|
||||||
createTime: now - 1000 * 60 * 60 * 6,
|
|
||||||
creatorName: '李珊珊',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(db.serviceRecordsByArchiveId[archiveId]) || db.serviceRecordsByArchiveId[archiveId].length === 0) {
|
|
||||||
const now = Date.now();
|
|
||||||
db.serviceRecordsByArchiveId[archiveId] = Array.from({ length: 18 }).map((_, idx) => {
|
|
||||||
const eventType =
|
|
||||||
idx % 5 === 0 ? 'questionnaire' : idx % 4 === 0 ? 'article' : idx % 3 === 0 ? 'sms' : 'phone';
|
|
||||||
const executionTime = now - 1000 * 60 * 60 * (idx * 6 + 3);
|
|
||||||
const executeTeamId = idx % 2 === 0 ? 'team_1' : 'team_2';
|
|
||||||
const executeTeamName = executeTeamId === 'team_1' ? '口腔一科(示例)' : '正畸团队(示例)';
|
|
||||||
return {
|
|
||||||
_id: uid('sr'),
|
|
||||||
eventType,
|
|
||||||
taskContent: `服务内容示例 #${idx + 1}:这里是任务描述,支持长文本展开收起。`,
|
|
||||||
result: idx % 7 === 0 ? '已联系患者,已确认到院时间。' : '',
|
|
||||||
executorName: idx % 2 === 0 ? '李医生' : '王护士',
|
|
||||||
executeTeamId,
|
|
||||||
executeTeamName,
|
|
||||||
executionTime,
|
|
||||||
pannedEventSendFile:
|
|
||||||
eventType === 'article'
|
|
||||||
? { type: 'article', url: 'https://example.com/article/1' }
|
|
||||||
: eventType === 'questionnaire'
|
|
||||||
? { type: 'questionnaire', surveryId: 'q_1' }
|
|
||||||
: null,
|
|
||||||
archiveName: archive?.name || '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(db.followupsByArchiveId[archiveId]) || db.followupsByArchiveId[archiveId].length === 0) {
|
|
||||||
const now = Date.now();
|
|
||||||
db.followupsByArchiveId[archiveId] = Array.from({ length: 22 }).map((_, idx) => {
|
|
||||||
const plannedExecutionTime = now + 1000 * 60 * 60 * 24 * ((idx % 9) - 2);
|
|
||||||
const createTime = now - 1000 * 60 * 60 * (idx * 5 + 2);
|
|
||||||
const statusPool = ['processing', 'notStart', 'treated', 'cancelled', 'expired'];
|
|
||||||
const status = statusPool[idx % statusPool.length];
|
|
||||||
const eventTypePool = ['followup', 'revisit', 'questionnaire', 'other'];
|
|
||||||
const eventType = eventTypePool[idx % eventTypePool.length];
|
|
||||||
const executeTeamId = idx % 2 === 0 ? 'team_1' : 'team_2';
|
|
||||||
const executeTeamName = executeTeamId === 'team_1' ? '口腔一科(示例)' : '正畸团队(示例)';
|
|
||||||
return {
|
|
||||||
_id: uid('td'),
|
|
||||||
plannedExecutionTime,
|
|
||||||
planDate: dayjs(plannedExecutionTime).format('YYYY-MM-DD'),
|
|
||||||
createTime,
|
|
||||||
createTimeStr: dayjs(createTime).format('YYYY-MM-DD HH:mm'),
|
|
||||||
executorName: idx % 2 === 0 ? '李医生' : '王护士',
|
|
||||||
executeTeamId,
|
|
||||||
executeTeamName,
|
|
||||||
creatorName: idx % 3 === 0 ? '系统' : '管理员A',
|
|
||||||
status,
|
|
||||||
eventType,
|
|
||||||
eventTypeLabel:
|
|
||||||
eventType === 'followup' ? '回访' : eventType === 'revisit' ? '复诊提醒' : eventType === 'questionnaire' ? '问卷' : '其他',
|
|
||||||
eventStatusLabel:
|
|
||||||
status === 'processing'
|
|
||||||
? '待处理'
|
|
||||||
: status === 'notStart'
|
|
||||||
? '未开始'
|
|
||||||
: status === 'treated'
|
|
||||||
? '已完成'
|
|
||||||
: status === 'cancelled'
|
|
||||||
? '已取消'
|
|
||||||
: '已过期',
|
|
||||||
taskContent: `回访任务示例 #${idx + 1}:电话回访/提醒到院等。`,
|
|
||||||
result: status === 'treated' ? '已完成回访,患者反馈良好。' : '',
|
|
||||||
archiveName: archive?.name || '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setDb(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurrentTeamId() {
|
|
||||||
const v = uni.getStorageSync('ykt_mock_current_team_id');
|
|
||||||
return v ? String(v) : 'team_1';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSortTimeTitle(templateType) {
|
|
||||||
if (templateType === 'outpatient') return 'visitTime';
|
|
||||||
if (templateType === 'inhospital') return 'inhosDate';
|
|
||||||
if (templateType === 'preConsultation') return 'consultDate';
|
|
||||||
if (templateType === 'physicalExaminationTemplate') return 'inspectDate';
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryVisitRecords({ archiveId, medicalType = 'ALL', timeRange = 'ALL', teamId = '', shareAllTeams = false }) {
|
|
||||||
const db = getDb();
|
|
||||||
const list = Array.isArray(db.visitRecordsByArchiveId[archiveId]) ? db.visitRecordsByArchiveId[archiveId] : [];
|
|
||||||
|
|
||||||
const withDate = list.map((i) => {
|
|
||||||
const type = i.templateType || i.medicalType || '';
|
|
||||||
const timeTitle = getSortTimeTitle(type);
|
|
||||||
const rawDate = timeTitle ? i[timeTitle] : '';
|
|
||||||
const fallback = i.sortTime ? dayjs(i.sortTime).format('YYYY-MM-DD') : '';
|
|
||||||
const date = rawDate || fallback;
|
|
||||||
return {
|
|
||||||
...i,
|
|
||||||
dateStr: date ? String(date) : '',
|
|
||||||
date: date ? String(date) : '',
|
|
||||||
createDateStr: i.createTime ? dayjs(i.createTime).format('YYYY-MM-DD') : '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let filtered = [...withDate];
|
|
||||||
if (medicalType !== 'ALL') filtered = filtered.filter((i) => (i.medicalType || i.templateType) === medicalType);
|
|
||||||
if (!shareAllTeams && teamId) filtered = filtered.filter((i) => !i.teamId || i.teamId === teamId);
|
|
||||||
|
|
||||||
if (timeRange && timeRange !== 'ALL') {
|
|
||||||
const days = timeRange === 'today' ? 0 : timeRange === '7d' ? 7 : timeRange === '30d' ? 30 : null;
|
|
||||||
if (days !== null) {
|
|
||||||
const start = days === 0 ? dayjs().startOf('day') : dayjs().subtract(days, 'day').startOf('day');
|
|
||||||
const startMs = start.valueOf();
|
|
||||||
filtered = filtered.filter((i) => (i.sortTime || 0) >= startMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filtered.sort((a, b) => (b.sortTime || 0) - (a.sortTime || 0));
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getVisitRecord({ archiveId, id }) {
|
|
||||||
const db = getDb();
|
|
||||||
const list = Array.isArray(db.visitRecordsByArchiveId[archiveId]) ? db.visitRecordsByArchiveId[archiveId] : [];
|
|
||||||
return list.find((i) => i._id === id) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function upsertVisitRecord({ archiveId, record }) {
|
|
||||||
const db = getDb();
|
|
||||||
const list = Array.isArray(db.visitRecordsByArchiveId[archiveId]) ? db.visitRecordsByArchiveId[archiveId] : [];
|
|
||||||
const next = { ...record };
|
|
||||||
if (!next._id) next._id = uid('mr');
|
|
||||||
if (!next.sortTime) next.sortTime = Date.now();
|
|
||||||
if (!next.createTime) next.createTime = Date.now();
|
|
||||||
|
|
||||||
const idx = list.findIndex((i) => i._id === next._id);
|
|
||||||
if (idx >= 0) list[idx] = { ...list[idx], ...next };
|
|
||||||
else list.unshift(next);
|
|
||||||
db.visitRecordsByArchiveId[archiveId] = list;
|
|
||||||
setDb(db);
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeVisitRecord({ archiveId, id }) {
|
|
||||||
const db = getDb();
|
|
||||||
const list = Array.isArray(db.visitRecordsByArchiveId[archiveId]) ? db.visitRecordsByArchiveId[archiveId] : [];
|
|
||||||
db.visitRecordsByArchiveId[archiveId] = list.filter((i) => i._id !== id);
|
|
||||||
setDb(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryServiceRecords({ archiveId, page = 1, pageSize = 10, eventType = 'ALL', teamId = 'ALL', dateRange = [] }) {
|
|
||||||
const db = getDb();
|
|
||||||
const list = Array.isArray(db.serviceRecordsByArchiveId[archiveId]) ? db.serviceRecordsByArchiveId[archiveId] : [];
|
|
||||||
|
|
||||||
let filtered = [...list];
|
|
||||||
if (eventType !== 'ALL') filtered = filtered.filter((i) => i.eventType === eventType);
|
|
||||||
if (teamId !== 'ALL') filtered = filtered.filter((i) => i.executeTeamId === teamId);
|
|
||||||
|
|
||||||
if (Array.isArray(dateRange) && dateRange.length === 2 && dateRange[0] && dateRange[1]) {
|
|
||||||
filtered = filtered.filter((i) => {
|
|
||||||
const d = i.executionTime ? dayjs(i.executionTime).format('YYYY-MM-DD') : '';
|
|
||||||
return d >= dateRange[0] && d <= dateRange[1];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
filtered.sort((a, b) => (b.executionTime || 0) - (a.executionTime || 0));
|
|
||||||
const total = filtered.length;
|
|
||||||
const pages = Math.ceil(total / pageSize) || 1;
|
|
||||||
const start = (page - 1) * pageSize;
|
|
||||||
const slice = filtered.slice(start, start + pageSize);
|
|
||||||
return { list: slice, total, pages };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getServiceRecord({ archiveId, id }) {
|
|
||||||
const db = getDb();
|
|
||||||
const list = Array.isArray(db.serviceRecordsByArchiveId[archiveId]) ? db.serviceRecordsByArchiveId[archiveId] : [];
|
|
||||||
return list.find((i) => i._id === id) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function upsertServiceRecord({ archiveId, record }) {
|
|
||||||
const db = getDb();
|
|
||||||
const list = Array.isArray(db.serviceRecordsByArchiveId[archiveId]) ? db.serviceRecordsByArchiveId[archiveId] : [];
|
|
||||||
const next = { ...record };
|
|
||||||
if (!next._id) next._id = uid('sr');
|
|
||||||
if (!next.executionTime) next.executionTime = Date.now();
|
|
||||||
|
|
||||||
const idx = list.findIndex((i) => i._id === next._id);
|
|
||||||
if (idx >= 0) list[idx] = { ...list[idx], ...next };
|
|
||||||
else list.unshift(next);
|
|
||||||
db.serviceRecordsByArchiveId[archiveId] = list;
|
|
||||||
setDb(db);
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeServiceRecord({ archiveId, id }) {
|
|
||||||
const db = getDb();
|
|
||||||
const list = Array.isArray(db.serviceRecordsByArchiveId[archiveId]) ? db.serviceRecordsByArchiveId[archiveId] : [];
|
|
||||||
db.serviceRecordsByArchiveId[archiveId] = list.filter((i) => i._id !== id);
|
|
||||||
setDb(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryFollowups({ archiveId, page = 1, pageSize = 10, status = 'all', isMy = false, eventTypes = [], teamId = 'ALL', planRange = ['', ''] }) {
|
|
||||||
const db = getDb();
|
|
||||||
const list = Array.isArray(db.followupsByArchiveId[archiveId]) ? db.followupsByArchiveId[archiveId] : [];
|
|
||||||
|
|
||||||
let filtered = [...list];
|
|
||||||
if (status !== 'all') filtered = filtered.filter((i) => i.status === status);
|
|
||||||
if (isMy) filtered = filtered.filter((i) => i.executorName === '李医生');
|
|
||||||
if (Array.isArray(eventTypes) && eventTypes.length) filtered = filtered.filter((i) => eventTypes.includes(i.eventType));
|
|
||||||
if (teamId !== 'ALL') filtered = filtered.filter((i) => i.executeTeamId === teamId);
|
|
||||||
if (planRange && (planRange[0] || planRange[1])) {
|
|
||||||
if (planRange[0]) filtered = filtered.filter((i) => i.planDate >= planRange[0]);
|
|
||||||
if (planRange[1]) filtered = filtered.filter((i) => i.planDate <= planRange[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
filtered.sort((a, b) => (b.plannedExecutionTime || 0) - (a.plannedExecutionTime || 0));
|
|
||||||
|
|
||||||
const total = filtered.length;
|
|
||||||
const pages = Math.ceil(total / pageSize) || 1;
|
|
||||||
const start = (page - 1) * pageSize;
|
|
||||||
const slice = filtered.slice(start, start + pageSize);
|
|
||||||
return { list: slice, total, pages };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFollowup({ archiveId, id }) {
|
|
||||||
const db = getDb();
|
|
||||||
const list = Array.isArray(db.followupsByArchiveId[archiveId]) ? db.followupsByArchiveId[archiveId] : [];
|
|
||||||
return list.find((i) => i._id === id) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function upsertFollowup({ archiveId, followup }) {
|
|
||||||
const db = getDb();
|
|
||||||
const list = Array.isArray(db.followupsByArchiveId[archiveId]) ? db.followupsByArchiveId[archiveId] : [];
|
|
||||||
const next = { ...followup };
|
|
||||||
if (!next._id) next._id = uid('td');
|
|
||||||
if (!next.plannedExecutionTime) next.plannedExecutionTime = Date.now();
|
|
||||||
next.planDate = dayjs(next.plannedExecutionTime).format('YYYY-MM-DD');
|
|
||||||
if (!next.createTime) next.createTime = Date.now();
|
|
||||||
next.createTimeStr = dayjs(next.createTime).format('YYYY-MM-DD HH:mm');
|
|
||||||
|
|
||||||
const idx = list.findIndex((i) => i._id === next._id);
|
|
||||||
if (idx >= 0) list[idx] = { ...list[idx], ...next };
|
|
||||||
else list.unshift(next);
|
|
||||||
|
|
||||||
db.followupsByArchiveId[archiveId] = list;
|
|
||||||
setDb(db);
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removeFollowup({ archiveId, id }) {
|
|
||||||
const db = getDb();
|
|
||||||
const list = Array.isArray(db.followupsByArchiveId[archiveId]) ? db.followupsByArchiveId[archiveId] : [];
|
|
||||||
db.followupsByArchiveId[archiveId] = list.filter((i) => i._id !== id);
|
|
||||||
setDb(db);
|
|
||||||
}
|
|
||||||
@ -2,14 +2,14 @@
|
|||||||
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/customer-detail/service-info/service-info.vue -->
|
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/customer-detail/service-info/service-info.vue -->
|
||||||
<view class="wrap">
|
<view class="wrap">
|
||||||
<view class="filters">
|
<view class="filters">
|
||||||
<picker mode="selector" :range="typeList" range-key="label" @change="pickType">
|
<picker class="filter-item" mode="selector" :range="typeList" range-key="label" @change="pickType">
|
||||||
<view class="filter-pill">
|
<view class="filter-pill">
|
||||||
<view class="pill-text" :class="{ muted: currentType.value === 'ALL' }">{{ currentType.value === 'ALL' ? '服务类型' : currentType.label }}</view>
|
<view class="pill-text" :class="{ muted: currentType.value === 'ALL' }">{{ currentType.value === 'ALL' ? '服务类型' : currentType.label }}</view>
|
||||||
<uni-icons type="arrowdown" size="12" color="#666" />
|
<uni-icons type="arrowdown" size="12" color="#666" />
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
</picker>
|
||||||
|
|
||||||
<uni-datetime-picker v-model="dateRange" type="daterange" rangeSeparator="-" @change="changeDates">
|
<uni-datetime-picker class="filter-item wide-item" v-model="dateRange" type="daterange" rangeSeparator="-" @change="changeDates">
|
||||||
<view class="filter-pill wide">
|
<view class="filter-pill wide">
|
||||||
<view class="pill-text" :class="{ muted: !dateRange.length }">
|
<view class="pill-text" :class="{ muted: !dateRange.length }">
|
||||||
{{ dateRange.length ? dateRange.join('~') : '服务时间' }}
|
{{ dateRange.length ? dateRange.join('~') : '服务时间' }}
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</uni-datetime-picker>
|
</uni-datetime-picker>
|
||||||
|
|
||||||
<picker mode="selector" :range="teamList" range-key="label" @change="pickTeam">
|
<picker class="filter-item" mode="selector" :range="teamList" range-key="label" @change="pickTeam">
|
||||||
<view class="filter-pill">
|
<view class="filter-pill">
|
||||||
<view class="pill-text">{{ currentTeam.label }}</view>
|
<view class="pill-text">{{ currentTeam.label }}</view>
|
||||||
<uni-icons type="arrowdown" size="12" color="#666" />
|
<uni-icons type="arrowdown" size="12" color="#666" />
|
||||||
@ -401,56 +401,56 @@ watch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filters {
|
.filters {
|
||||||
padding: 6px 14px;
|
padding: 10px 14px;
|
||||||
background: #f5f6f8;
|
background: #fff;
|
||||||
border-bottom: 1px solid #f2f2f2;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: nowrap;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.filters :deep(picker) {
|
.filter-item {
|
||||||
display: block;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
.filters :deep(uni-datetime-picker) {
|
|
||||||
display: block;
|
display: block;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
margin: 0 12px;
|
|
||||||
}
|
}
|
||||||
|
.filter-item.wide-item {
|
||||||
|
flex: 1.4;
|
||||||
|
}
|
||||||
|
/* Removed old deep selectors */
|
||||||
.filter-pill {
|
.filter-pill {
|
||||||
background: #fff;
|
background: #f7f8fa;
|
||||||
border: 1px solid #e6e6e6;
|
border: 1px solid #e5e7eb;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
padding: 8px 10px;
|
padding: 10px 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
gap: 8px;
|
||||||
min-width: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
|
||||||
.filter-pill.wide {
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
box-sizing: border-box; /* Ensure padding doesn't overflow width */
|
||||||
}
|
}
|
||||||
.pill-text {
|
.pill-text {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #333;
|
color: #333;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
margin-right: 8px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
font-weight: 500;
|
||||||
.filter-pill :deep(uni-icons) {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
.pill-text.muted {
|
.pill-text.muted {
|
||||||
color: #999;
|
color: #999;
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
.pill-icon {
|
.pill-icon {
|
||||||
padding-left: 8px;
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.filter-pill :deep(uni-icons) {
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline {
|
.timeline {
|
||||||
|
|||||||
62
components/archive-detail/templates.js
Normal file
62
components/archive-detail/templates.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
export const VISIT_RECORD_TEMPLATES = [
|
||||||
|
{
|
||||||
|
templateType: 'outpatient',
|
||||||
|
templateName: '门诊记录',
|
||||||
|
service: { timeTitle: 'visitTime', timeName: '就诊日期' },
|
||||||
|
templateList: [
|
||||||
|
{ title: 'visitTime', name: '就诊日期', type: 'date', operateType: 'formCell', required: false, format: 'YYYY-MM-DD', placeholder: '请选择就诊日期' },
|
||||||
|
{ title: 'chiefComplaint', name: '主诉', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请输入或说话录音转录问题', rows: 1, autoHeight: true },
|
||||||
|
{ title: 'medicalHistory', name: '病史概要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '请简述患者情况,量键既往病史、用药史等', rows: 3, autoHeight: true },
|
||||||
|
{ title: 'examination', name: '检查', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '请填写关键项目或异常结果描述', rows: 1, autoHeight: true },
|
||||||
|
{ title: 'diagnosis', name: '诊断', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请填写诊断名称', supportVoice: true, rows: 1, autoHeight: true },
|
||||||
|
{ title: 'treatmentPlan', name: '治疗方案', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 1000, placeholder: '请简述治疗方案及效果', rows: 3, autoHeight: true },
|
||||||
|
{ title: 'files', name: '文件上传', type: 'files', required: false, desc: '(支持≤5M文件,pdf文件格式)', maxSize: 5, accept: 'pdf' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
templateType: 'inhospital',
|
||||||
|
templateName: '住院记录',
|
||||||
|
service: { timeTitle: 'inhosDate', timeName: '入院日期' },
|
||||||
|
templateList: [
|
||||||
|
{ title: 'inhosDate', name: '入院日期', type: 'date', operateType: 'formCell', required: false, format: 'YYYY-MM-DD', placeholder: '请选择入院日期' },
|
||||||
|
{ title: 'chiefComplaint', name: '主诉', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请输入病症与转诊问题', rows: 1, autoHeight: true },
|
||||||
|
{ title: 'medicalHistory', name: '病史概要', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '请简述患者情况,量键既往病史、用药史等症状', rows: 3, autoHeight: true },
|
||||||
|
{ title: 'examination', name: '检查', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '请填写关键项目或异常结果描述', rows: 1, autoHeight: true },
|
||||||
|
{ title: 'diagnosis', name: '住院主诊断', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请填写诊断名称', rows: 1, autoHeight: true },
|
||||||
|
{ title: 'surgeryDate', name: '手术日期', type: 'date', operateType: 'formCell', required: false, format: 'YYYY-MM-DD', placeholder: '请选择手术日期' },
|
||||||
|
{ title: 'surgeryName', name: '手术名称', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 100, placeholder: '请填写手术名称', rows: 1, autoHeight: true },
|
||||||
|
{ title: 'treatmentPlan', name: '治疗方案', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 1000, placeholder: '请简述治疗方案及效果', rows: 3, autoHeight: true },
|
||||||
|
{ title: 'files', name: '文件上传', type: 'files', required: false, desc: '(支持≤5M文件,pdf文件格式)', maxSize: 5, accept: 'pdf' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
templateType: 'preConsultation',
|
||||||
|
templateName: '预问诊记录',
|
||||||
|
service: { timeTitle: 'consultDate', timeName: '问诊日期' },
|
||||||
|
templateList: [
|
||||||
|
{ title: 'chiefComplaint', name: '主诉', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请输入症状及转诊问题', rows: 1, autoHeight: true },
|
||||||
|
{ title: 'presentIllness', name: '现病史', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 800, placeholder: '请述述发病的过程、发展、诊疗经过及当前病情', rows: 3, autoHeight: true },
|
||||||
|
{ title: 'pastHistory', name: '既往史', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 800, placeholder: '请填写既往疾病、手术/外伤史、药物/食物过敏史', rows: 3, autoHeight: true },
|
||||||
|
{ title: 'files', name: '文件上传', type: 'files', required: false, desc: '(支持≤5M文件,pdf文件格式)', maxSize: 5, accept: 'pdf' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
templateType: 'physicalExaminationTemplate',
|
||||||
|
templateName: '体检记录',
|
||||||
|
service: { timeTitle: 'inspectDate', timeName: '体检日期' },
|
||||||
|
templateList: [
|
||||||
|
{ title: 'inspectDate', name: '体检日期', type: 'date', operateType: 'formCell', required: false, format: 'YYYY-MM-DD', placeholder: '请选择体检日期' },
|
||||||
|
{ title: 'summary', name: '体检小结', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 200, placeholder: '请填写本次体检的小结', rows: 3, autoHeight: true },
|
||||||
|
{ title: 'positiveFind', name: '阳性发现及处理意见', type: 'textarea', operateType: 'formCell', required: false, wordLimit: 500, placeholder: '可参照医客通现有模式', rows: 3, refNote: '可参照医客通现有模式', autoHeight: true },
|
||||||
|
{ title: 'files', name: '文件上传', type: 'files', required: false, desc: '(支持≤5M文件,pdf文件格式)', maxSize: 5, accept: 'pdf' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getVisitRecordTemplates() {
|
||||||
|
return VISIT_RECORD_TEMPLATES.map((i) => ({ templateType: i.templateType, name: i.templateName, templateList: i.templateList }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVisitRecordTemplate(templateType) {
|
||||||
|
return VISIT_RECORD_TEMPLATES.find((i) => i.templateType === templateType) || null;
|
||||||
|
}
|
||||||
103
components/form-template/form-cell/form-diagnosis-picker.vue
Normal file
103
components/form-template/form-cell/form-diagnosis-picker.vue
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<view class="row" @click="open">
|
||||||
|
<view class="left">
|
||||||
|
<text class="label">{{ name }}</text>
|
||||||
|
<text v-if="required" class="required">*</text>
|
||||||
|
</view>
|
||||||
|
<view class="right">
|
||||||
|
<view class="value" :class="{ muted: !displayText }">{{ displayText || placeholder }}</view>
|
||||||
|
<uni-icons type="arrowright" size="16" color="#999" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onUnmounted, ref } from 'vue';
|
||||||
|
|
||||||
|
const emits = defineEmits(['change']);
|
||||||
|
const props = defineProps({
|
||||||
|
form: { type: Object, default: () => ({}) },
|
||||||
|
name: { default: '' },
|
||||||
|
required: { type: Boolean, default: false },
|
||||||
|
title: { default: '' },
|
||||||
|
disableChange: { type: Boolean, default: false },
|
||||||
|
placeholder: { default: '' },
|
||||||
|
mult: { type: Boolean, default: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
const placeholder = computed(() => (props.placeholder ? String(props.placeholder) : `请选择${props.name || ''}`));
|
||||||
|
const rawValue = computed(() => props.form?.[props.title]);
|
||||||
|
const displayText = computed(() => {
|
||||||
|
const v = rawValue.value;
|
||||||
|
if (Array.isArray(v)) return v.filter((i) => i !== null && i !== undefined && String(i).trim()).join(',');
|
||||||
|
return v ? String(v) : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeEventName = ref('');
|
||||||
|
function clearListener() {
|
||||||
|
if (activeEventName.value) uni.$off(activeEventName.value);
|
||||||
|
activeEventName.value = '';
|
||||||
|
}
|
||||||
|
onUnmounted(clearListener);
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
if (props.disableChange) return;
|
||||||
|
clearListener();
|
||||||
|
activeEventName.value = `diagnosis-change_${Date.now()}`;
|
||||||
|
uni.$on(activeEventName.value, (data) => {
|
||||||
|
emits('change', { title: props.title, value: data });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (props.mult) {
|
||||||
|
const cur = Array.isArray(rawValue.value) ? rawValue.value : [];
|
||||||
|
uni.setStorageSync('diagnosis-list-selection', cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/library/diagnosis-list?eventName=${encodeURIComponent(activeEventName.value)}&mult=${props.mult ? 'YES' : 'NO'}&value=${encodeURIComponent(displayText.value || '')}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #111827;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.required {
|
||||||
|
color: #ff4d4f;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 120px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #111827;
|
||||||
|
max-width: 220px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.muted {
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
168
components/form-template/form-cell/form-positive-find.vue
Normal file
168
components/form-template/form-cell/form-positive-find.vue
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
<template>
|
||||||
|
<view class="wrap">
|
||||||
|
<view class="head" @click="add">
|
||||||
|
<view class="head-left">
|
||||||
|
<text class="label">{{ name }}</text>
|
||||||
|
<text v-if="required" class="required">*</text>
|
||||||
|
</view>
|
||||||
|
<uni-icons type="plusempty" size="20" color="#4f6ef7" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="value.length" class="list">
|
||||||
|
<uni-swipe-action>
|
||||||
|
<uni-swipe-action-item v-for="(i, idx) in value" :key="idx">
|
||||||
|
<view class="item">
|
||||||
|
<view class="item-title">{{ idx + 1 }}、{{ i.category || '' }}</view>
|
||||||
|
<view class="item-sub">{{ i.opinion || '' }}</view>
|
||||||
|
</view>
|
||||||
|
<template #right>
|
||||||
|
<view class="actions">
|
||||||
|
<view class="action edit" @click.stop="edit(i, idx)">编辑</view>
|
||||||
|
<view class="action del" @click.stop="remove(idx)">删除</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</uni-swipe-action-item>
|
||||||
|
</uni-swipe-action>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else class="empty">暂无内容,点击右侧 + 添加</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onUnmounted, ref } from 'vue';
|
||||||
|
import { confirm, toast } from '@/utils/widget';
|
||||||
|
|
||||||
|
const emits = defineEmits(['change']);
|
||||||
|
const props = defineProps({
|
||||||
|
form: { type: Object, default: () => ({}) },
|
||||||
|
name: { default: '' },
|
||||||
|
required: { type: Boolean, default: false },
|
||||||
|
title: { default: '' },
|
||||||
|
disableChange: { type: Boolean, default: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
const value = computed(() => {
|
||||||
|
const arr = props.form?.[props.title];
|
||||||
|
return Array.isArray(arr) ? arr.filter((i) => i && (i.category || i.opinion)) : [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeEventName = ref('');
|
||||||
|
function clearListener() {
|
||||||
|
if (activeEventName.value) uni.$off(activeEventName.value);
|
||||||
|
activeEventName.value = '';
|
||||||
|
}
|
||||||
|
onUnmounted(clearListener);
|
||||||
|
|
||||||
|
function emitChange(next) {
|
||||||
|
emits('change', { title: props.title, value: next });
|
||||||
|
}
|
||||||
|
|
||||||
|
function add() {
|
||||||
|
if (props.disableChange) return;
|
||||||
|
uni.setStorageSync('current-positive-find', { category: '', opinion: '' });
|
||||||
|
clearListener();
|
||||||
|
activeEventName.value = `positive-find-change_${Date.now()}`;
|
||||||
|
uni.$on(activeEventName.value, (data) => {
|
||||||
|
const list = value.value.map((i) => ({ category: i.category, opinion: i.opinion }));
|
||||||
|
list.push({ category: String(data?.category || ''), opinion: String(data?.opinion || '') });
|
||||||
|
emitChange(list);
|
||||||
|
});
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/others/edit-positive-find?eventName=${encodeURIComponent(activeEventName.value)}&title=${encodeURIComponent(props.name || '阳性发现')}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function edit(item, idx) {
|
||||||
|
if (props.disableChange) return;
|
||||||
|
uni.setStorageSync('current-positive-find', { category: item?.category || '', opinion: item?.opinion || '' });
|
||||||
|
clearListener();
|
||||||
|
activeEventName.value = `positive-find-change_${Date.now()}`;
|
||||||
|
uni.$on(activeEventName.value, (data) => {
|
||||||
|
const list = value.value.map((i) => ({ category: i.category, opinion: i.opinion }));
|
||||||
|
list[idx] = { category: String(data?.category || ''), opinion: String(data?.opinion || '') };
|
||||||
|
emitChange(list);
|
||||||
|
});
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/others/edit-positive-find?eventName=${encodeURIComponent(activeEventName.value)}&title=${encodeURIComponent(props.name || '阳性发现')}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(idx) {
|
||||||
|
if (props.disableChange) return;
|
||||||
|
confirm('确定删除吗?', () => {
|
||||||
|
const list = value.value.map((i) => ({ category: i.category, opinion: i.opinion }));
|
||||||
|
list.splice(idx, 1);
|
||||||
|
emitChange(list);
|
||||||
|
toast('已删除');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.wrap {
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
.head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.head-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
.required {
|
||||||
|
color: #ff4d4f;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
.item-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #111827;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.item-sub {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.action {
|
||||||
|
width: 70px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.action.edit {
|
||||||
|
background: #4f6ef7;
|
||||||
|
}
|
||||||
|
.action.del {
|
||||||
|
background: #ff4d4f;
|
||||||
|
}
|
||||||
|
.empty {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@ -4,8 +4,16 @@
|
|||||||
{{ name }}<text v-if="required" class="form-cell--required"></text>
|
{{ name }}<text v-if="required" class="form-cell--required"></text>
|
||||||
</view>
|
</view>
|
||||||
<view class="mt-10">
|
<view class="mt-10">
|
||||||
<textarea :disabled="disableChange" :value="value" class="form-textarea" :placeholder="placeholder"
|
<textarea
|
||||||
placeholder-class="form__placeholder" :maxlength="wordLimit" @input="change($event)" />
|
:disabled="disableChange"
|
||||||
|
:value="value"
|
||||||
|
class="form-textarea"
|
||||||
|
:style="textareaStyle"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
placeholder-class="form__placeholder"
|
||||||
|
:maxlength="wordLimit"
|
||||||
|
:auto-height="autoHeight"
|
||||||
|
@input="change($event)" />
|
||||||
<view v-if="wordLimit > 0" class="form-textarea__count">
|
<view v-if="wordLimit > 0" class="form-textarea__count">
|
||||||
{{ value && value.length ? value.length : 0 }} / {{ wordLimit }}
|
{{ value && value.length ? value.length : 0 }} / {{ wordLimit }}
|
||||||
</view>
|
</view>
|
||||||
@ -38,6 +46,14 @@ const props = defineProps({
|
|||||||
wordLimit: {
|
wordLimit: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
default: 100
|
default: 100
|
||||||
|
},
|
||||||
|
autoHeight: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
rows: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 3
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -56,6 +72,13 @@ const wordLimit = computed(() => {
|
|||||||
return 100
|
return 100
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const textareaStyle = computed(() => {
|
||||||
|
const rowCount = typeof props.rows === 'number' ? props.rows : (Number(props.rows) || 3);
|
||||||
|
// 每行大约42rpx高度(28rpx字体 + 14rpx行距)
|
||||||
|
const minHeight = rowCount * 42;
|
||||||
|
return `min-height: ${minHeight}rpx;`;
|
||||||
|
})
|
||||||
|
|
||||||
function change(e) {
|
function change(e) {
|
||||||
emits('change', {
|
emits('change', {
|
||||||
title: props.title,
|
title: props.title,
|
||||||
|
|||||||
@ -1,6 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<form-positive-find
|
||||||
|
v-if="attrs.title === 'positiveFind'"
|
||||||
|
v-bind="attrs"
|
||||||
|
:form="form"
|
||||||
|
:disableChange="disableChange"
|
||||||
|
@change="change"
|
||||||
|
/>
|
||||||
|
<form-diagnosis-picker
|
||||||
|
v-else-if="attrs.title === 'diagnosis' || attrs.title === 'diagnosisName'"
|
||||||
|
v-bind="attrs"
|
||||||
|
:form="form"
|
||||||
|
:disableChange="disableChange"
|
||||||
|
@change="change"
|
||||||
|
/>
|
||||||
<form-surgical-history
|
<form-surgical-history
|
||||||
v-if="attrs.title === 'surgicalHistory'"
|
v-else-if="attrs.title === 'surgicalHistory'"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
:form="form"
|
:form="form"
|
||||||
:disableChange="disableChange"
|
:disableChange="disableChange"
|
||||||
@ -54,6 +68,8 @@ import formRunTime from './form-run-time.vue';
|
|||||||
import formFiles from './form-files.vue';
|
import formFiles from './form-files.vue';
|
||||||
import formSelectImage from './form-select-image.vue';
|
import formSelectImage from './form-select-image.vue';
|
||||||
import formSurgicalHistory from './form-surgical-history.vue';
|
import formSurgicalHistory from './form-surgical-history.vue';
|
||||||
|
import formPositiveFind from './form-positive-find.vue';
|
||||||
|
import formDiagnosisPicker from './form-diagnosis-picker.vue';
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
form: {
|
form: {
|
||||||
|
|||||||
12
pages.json
12
pages.json
@ -140,6 +140,18 @@
|
|||||||
"navigationBarTitleText": "执行回访计划"
|
"navigationBarTitleText": "执行回访计划"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/library/diagnosis-list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "诊断"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/others/edit-positive-find",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "阳性发现"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/work/work",
|
"path": "pages/work/work",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
@ -95,7 +95,7 @@
|
|||||||
:key="t.key"
|
:key="t.key"
|
||||||
class="tab"
|
class="tab"
|
||||||
:class="{ active: currentTab === t.key }"
|
:class="{ active: currentTab === t.key }"
|
||||||
@click="currentTab = t.key"
|
@click="switchTab(t.key)"
|
||||||
>
|
>
|
||||||
{{ t.title }}
|
{{ t.title }}
|
||||||
</view>
|
</view>
|
||||||
@ -224,7 +224,6 @@ import { storeToRefs } from 'pinia';
|
|||||||
import HealthProfileTab from '@/components/archive-detail/health-profile-tab.vue';
|
import HealthProfileTab from '@/components/archive-detail/health-profile-tab.vue';
|
||||||
import ServiceInfoTab from '@/components/archive-detail/service-info-tab.vue';
|
import ServiceInfoTab from '@/components/archive-detail/service-info-tab.vue';
|
||||||
import FollowUpManageTab from '@/components/archive-detail/follow-up-manage-tab.vue';
|
import FollowUpManageTab from '@/components/archive-detail/follow-up-manage-tab.vue';
|
||||||
import { ensureSeed } from '@/components/archive-detail/mock';
|
|
||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import useAccountStore from '@/store/account';
|
import useAccountStore from '@/store/account';
|
||||||
import { hideLoading, loading, toast } from '@/utils/widget';
|
import { hideLoading, loading, toast } from '@/utils/widget';
|
||||||
@ -245,6 +244,12 @@ const archiveId = ref('');
|
|||||||
const tabsScrollTop = ref(0);
|
const tabsScrollTop = ref(0);
|
||||||
const instanceProxy = getCurrentInstance()?.proxy;
|
const instanceProxy = getCurrentInstance()?.proxy;
|
||||||
|
|
||||||
|
function switchTab(key) {
|
||||||
|
currentTab.value = key;
|
||||||
|
// 切换 tab 后,将页面滚动到顶部,确保 tab 吸顶可见
|
||||||
|
uni.pageScrollTo({ scrollTop: 0, duration: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
const archive = ref({
|
const archive = ref({
|
||||||
name: '',
|
name: '',
|
||||||
sex: '',
|
sex: '',
|
||||||
@ -438,7 +443,6 @@ onLoad((options) => {
|
|||||||
if (!archiveId.value) {
|
if (!archiveId.value) {
|
||||||
archiveId.value = String(archive.value.medicalRecordNo || archive.value.outpatientNo || archive.value.inpatientNo || `mock_${Date.now()}`);
|
archiveId.value = String(archive.value.medicalRecordNo || archive.value.outpatientNo || archive.value.inpatientNo || `mock_${Date.now()}`);
|
||||||
}
|
}
|
||||||
ensureSeed(archiveId.value, archive.value);
|
|
||||||
|
|
||||||
// 接口兜底:优先用接口刷新档案详情
|
// 接口兜底:优先用接口刷新档案详情
|
||||||
fetchArchive();
|
fetchArchive();
|
||||||
@ -502,7 +506,8 @@ const idRows = computed(() => {
|
|||||||
|
|
||||||
const createText = computed(() => {
|
const createText = computed(() => {
|
||||||
const time = archive.value.createTime ? String(archive.value.createTime) : '';
|
const time = archive.value.createTime ? String(archive.value.createTime) : '';
|
||||||
const creator = archive.value.creator ? String(archive.value.creator) : '';
|
const rawCreator = archive.value.creator ? String(archive.value.creator) : '';
|
||||||
|
const creator = ['-', '—', '--'].includes(rawCreator.trim()) ? '' : rawCreator.trim();
|
||||||
if (time && creator) return `${time} ${creator}创建`;
|
if (time && creator) return `${time} ${creator}创建`;
|
||||||
if (time) return `${time} 创建`;
|
if (time) return `${time} 创建`;
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@ -13,7 +13,10 @@
|
|||||||
|
|
||||||
<!-- 附件上传(FormTemplate 不支持 files,单独实现) -->
|
<!-- 附件上传(FormTemplate 不支持 files,单独实现) -->
|
||||||
<view v-if="hasFilesField" class="upload-wrap">
|
<view v-if="hasFilesField" class="upload-wrap">
|
||||||
<view class="upload-title">文件上传</view>
|
<view class="upload-row">
|
||||||
|
<view class="upload-label">文件上传</view>
|
||||||
|
<view class="upload-desc">(支持≤5M文件,pdf文件格式)</view>
|
||||||
|
</view>
|
||||||
<view class="upload-grid">
|
<view class="upload-grid">
|
||||||
<view v-for="(f, idx) in fileList" :key="idx" class="upload-item" @click="previewFile(idx)">
|
<view v-for="(f, idx) in fileList" :key="idx" class="upload-item" @click="previewFile(idx)">
|
||||||
<image class="upload-thumb" :src="f.url" mode="aspectFill" />
|
<image class="upload-thumb" :src="f.url" mode="aspectFill" />
|
||||||
@ -44,12 +47,44 @@
|
|||||||
import { computed, reactive, ref } from 'vue';
|
import { computed, reactive, ref } from 'vue';
|
||||||
import { onLoad } from '@dcloudio/uni-app';
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
import FormTemplate from '@/components/form-template/index.vue';
|
import FormTemplate from '@/components/form-template/index.vue';
|
||||||
import { ensureSeed, getCurrentTeamId, getVisitRecord, getVisitRecordTemplate, removeVisitRecord, upsertVisitRecord } from '@/components/archive-detail/mock';
|
import { getVisitRecordTemplate } from '@/components/archive-detail/templates';
|
||||||
|
import api from '@/utils/api';
|
||||||
|
import useAccountStore from '@/store/account';
|
||||||
|
import { toast, confirm, loading as uniLoading, hideLoading } from '@/utils/widget';
|
||||||
|
|
||||||
const archiveId = ref('');
|
const accountStore = useAccountStore();
|
||||||
|
const { account, doctorInfo } = storeToRefs(accountStore);
|
||||||
|
const { getDoctorInfo } = accountStore;
|
||||||
|
|
||||||
|
async function ensureDoctor() {
|
||||||
|
if (doctorInfo.value) return;
|
||||||
|
if (!account.value?.openid) return;
|
||||||
|
try {
|
||||||
|
await getDoctorInfo();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUserId() {
|
||||||
|
const d = doctorInfo.value || {};
|
||||||
|
const a = account.value || {};
|
||||||
|
return String(d.userid || d.userId || d.corpUserId || a.userid || a.userId || '') || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCorpId() {
|
||||||
|
const d = doctorInfo.value || {};
|
||||||
|
const a = account.value || {};
|
||||||
|
const t = uni.getStorageSync('ykt_case_current_team') || {};
|
||||||
|
return String(d.corpId || a.corpId || t.corpId || '') || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const memberId = ref('');
|
||||||
const recordId = ref('');
|
const recordId = ref('');
|
||||||
const templateType = ref('');
|
const templateType = ref('');
|
||||||
|
const customerName = ref('');
|
||||||
|
|
||||||
const template = computed(() => getVisitRecordTemplate(templateType.value));
|
const template = computed(() => getVisitRecordTemplate(templateType.value));
|
||||||
const detail = ref({});
|
const detail = ref({});
|
||||||
@ -84,27 +119,67 @@ function ensureFilesField() {
|
|||||||
form.files = [];
|
form.files = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad((options) => {
|
onLoad(async (options) => {
|
||||||
archiveId.value = options?.archiveId ? String(options.archiveId) : '';
|
memberId.value = options?.memberId || options?.archiveId || '';
|
||||||
recordId.value = options?.id ? String(options.id) : '';
|
recordId.value = options?.id || '';
|
||||||
templateType.value = options?.type ? String(options.type) : '';
|
templateType.value = options?.type || '';
|
||||||
|
customerName.value = decodeURIComponent(options?.customerName || '');
|
||||||
ensureSeed(archiveId.value, { name: options?.name ? String(options.name) : '' });
|
|
||||||
|
|
||||||
if (recordId.value) {
|
if (recordId.value) {
|
||||||
const record = getVisitRecord({ archiveId: archiveId.value, id: recordId.value });
|
await getDetail();
|
||||||
if (record) {
|
} else {
|
||||||
templateType.value = record.templateType || record.medicalType || templateType.value;
|
|
||||||
detail.value = record;
|
|
||||||
ensureFilesField();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!templateType.value) templateType.value = 'outpatient';
|
if (!templateType.value) templateType.value = 'outpatient';
|
||||||
ensureFilesField();
|
ensureFilesField();
|
||||||
|
// 门诊记录默认今日日期
|
||||||
|
if (templateType.value === 'outpatient') {
|
||||||
|
form.visitTime = dayjs().format('YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
// 住院记录默认今日日期
|
||||||
|
if (templateType.value === 'inhospital') {
|
||||||
|
form.inhosDate = dayjs().format('YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
// 体检记录默认今日日期
|
||||||
|
if (templateType.value === 'physicalExaminationTemplate') {
|
||||||
|
form.inspectDate = dayjs().format('YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function getDetail() {
|
||||||
|
if (!recordId.value || !memberId.value) return;
|
||||||
|
await ensureDoctor();
|
||||||
|
const corpId = getCorpId();
|
||||||
|
if (!corpId) return;
|
||||||
|
uniLoading('加载中...');
|
||||||
|
try {
|
||||||
|
const res = await api('getMedicalRecordById', {
|
||||||
|
_id: recordId.value,
|
||||||
|
corpId,
|
||||||
|
memberId: memberId.value,
|
||||||
|
medicalType: templateType.value,
|
||||||
|
});
|
||||||
|
hideLoading();
|
||||||
|
const record = res?.record || res?.data?.record || null;
|
||||||
|
if (res?.success && record) {
|
||||||
|
templateType.value = record.templateType || record.medicalType || templateType.value;
|
||||||
|
|
||||||
|
// 兼容模板字段:wxapp 使用 diagnosis,但接口通常返回 diagnosisName
|
||||||
|
if ((record.medicalType === 'outpatient' || record.medicalType === 'inhospital') && !record.diagnosis && record.diagnosisName) {
|
||||||
|
record.diagnosis = record.diagnosisName;
|
||||||
|
}
|
||||||
|
|
||||||
|
detail.value = record;
|
||||||
|
ensureFilesField();
|
||||||
|
} else {
|
||||||
|
toast(res.message || '加载失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
hideLoading();
|
||||||
|
console.error('getDetail error:', error);
|
||||||
|
toast('加载失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onChange({ title, value }) {
|
function onChange({ title, value }) {
|
||||||
form[title] = value;
|
form[title] = value;
|
||||||
const item = showItems.value.find((i) => i.title === title);
|
const item = showItems.value.find((i) => i.title === title);
|
||||||
@ -118,67 +193,120 @@ function cancel() {
|
|||||||
uni.navigateBack();
|
uni.navigateBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
async function save() {
|
||||||
if (!archiveId.value) {
|
if (!memberId.value) {
|
||||||
uni.showToast({ title: '缺少 archiveId', icon: 'none' });
|
toast('缺少患者信息');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await ensureDoctor();
|
||||||
|
const corpId = getCorpId();
|
||||||
|
const userId = getUserId();
|
||||||
|
if (!corpId || !userId) return toast('缺少用户信息');
|
||||||
if (formRef.value?.verify && !formRef.value.verify()) return;
|
if (formRef.value?.verify && !formRef.value.verify()) return;
|
||||||
|
|
||||||
// sortTime:尽量用模板中的日期字段
|
const params = {
|
||||||
const timeTitle =
|
|
||||||
templateType.value === 'outpatient'
|
|
||||||
? 'visitTime'
|
|
||||||
: templateType.value === 'inhospital'
|
|
||||||
? 'inhosDate'
|
|
||||||
: templateType.value === 'preConsultation'
|
|
||||||
? 'consultDate'
|
|
||||||
: templateType.value === 'physicalExaminationTemplate'
|
|
||||||
? 'inspectDate'
|
|
||||||
: '';
|
|
||||||
const timeValue = timeTitle ? forms.value[timeTitle] : '';
|
|
||||||
const sortTime = timeValue && dayjs(timeValue).isValid() ? dayjs(timeValue).valueOf() : Date.now();
|
|
||||||
|
|
||||||
upsertVisitRecord({
|
|
||||||
archiveId: archiveId.value,
|
|
||||||
record: {
|
|
||||||
_id: recordId.value || '',
|
|
||||||
medicalType: templateType.value,
|
|
||||||
templateType: templateType.value,
|
|
||||||
tempName: template.value?.templateName || '健康档案',
|
|
||||||
sortTime,
|
|
||||||
teamId: detail.value?.teamId || getCurrentTeamId(),
|
|
||||||
...form,
|
...form,
|
||||||
files: fileList.value,
|
corpId,
|
||||||
createTime: detail.value?.createTime || Date.now(),
|
memberId: memberId.value,
|
||||||
creatorName: detail.value?.creatorName || '我',
|
medicalType: templateType.value,
|
||||||
},
|
};
|
||||||
});
|
|
||||||
|
// 门诊/住院:与 mobile 字段对齐(diagnosisName)
|
||||||
|
if ((templateType.value === 'outpatient' || templateType.value === 'inhospital') && form.diagnosis && !form.diagnosisName) {
|
||||||
|
params.diagnosisName = form.diagnosis;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recordId.value) {
|
||||||
|
params._id = recordId.value;
|
||||||
|
params.userId = userId;
|
||||||
|
} else {
|
||||||
|
params.creator = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortTime:使用模板中的时间字段
|
||||||
|
const sortTimeKey = template.value?.service?.timeTitle || '';
|
||||||
|
if (sortTimeKey && form[sortTimeKey] && dayjs(form[sortTimeKey]).isValid()) {
|
||||||
|
params.sortTime = dayjs(form[sortTimeKey]).valueOf();
|
||||||
|
} else {
|
||||||
|
params.sortTime = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
uniLoading('保存中...');
|
||||||
|
try {
|
||||||
|
const res = await api(
|
||||||
|
recordId.value ? 'updateMedicalRecord' : 'addMedicalRecord',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
hideLoading();
|
||||||
|
if (res.success) {
|
||||||
uni.$emit('archive-detail:visit-record-changed');
|
uni.$emit('archive-detail:visit-record-changed');
|
||||||
uni.showToast({ title: '保存成功', icon: 'success' });
|
toast(res.message || '保存成功');
|
||||||
setTimeout(() => uni.navigateBack(), 300);
|
setTimeout(() => uni.navigateBack(), 300);
|
||||||
|
} else {
|
||||||
|
toast(res.message || '保存失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
hideLoading();
|
||||||
|
console.error('save error:', error);
|
||||||
|
toast('保存失败');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove() {
|
function remove() {
|
||||||
uni.showModal({
|
confirm('确定删除当前记录?', async () => {
|
||||||
title: '提示',
|
if (!memberId.value || !recordId.value) return toast('缺少必要信息');
|
||||||
content: '确定删除当前记录?',
|
await ensureDoctor();
|
||||||
success: (res) => {
|
const corpId = getCorpId();
|
||||||
if (!res.confirm) return;
|
if (!corpId) return toast('缺少必要信息');
|
||||||
removeVisitRecord({ archiveId: archiveId.value, id: recordId.value });
|
uniLoading('删除中...');
|
||||||
|
try {
|
||||||
|
const res = await api('removeMedicalRecord', {
|
||||||
|
corpId,
|
||||||
|
memberId: memberId.value,
|
||||||
|
medicalType: templateType.value,
|
||||||
|
_id: recordId.value,
|
||||||
|
});
|
||||||
|
hideLoading();
|
||||||
|
if (res.success) {
|
||||||
uni.$emit('archive-detail:visit-record-changed');
|
uni.$emit('archive-detail:visit-record-changed');
|
||||||
uni.showToast({ title: '已删除', icon: 'success' });
|
toast(res.message || '已删除');
|
||||||
setTimeout(() => uni.navigateBack(), 300);
|
setTimeout(() => uni.navigateBack(), 300);
|
||||||
},
|
} else {
|
||||||
|
toast(res.message || '删除失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
hideLoading();
|
||||||
|
console.error('remove error:', error);
|
||||||
|
toast('删除失败');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addFiles() {
|
function addFiles() {
|
||||||
uni.chooseImage({
|
const fileConfig = template.value?.templateList?.find(i => i.type === 'files');
|
||||||
|
const maxSize = fileConfig?.maxSize || 5; // MB
|
||||||
|
const accept = fileConfig?.accept || 'pdf';
|
||||||
|
|
||||||
|
uni.chooseMessageFile({
|
||||||
count: 9,
|
count: 9,
|
||||||
|
type: 'file',
|
||||||
|
extension: [accept],
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
const paths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [];
|
const files = Array.isArray(res.tempFiles) ? res.tempFiles : [];
|
||||||
const next = paths.map((p) => ({ url: p, name: '' }));
|
const maxBytes = maxSize * 1024 * 1024;
|
||||||
|
|
||||||
|
// 验证文件大小
|
||||||
|
const invalidFiles = files.filter(f => f.size > maxBytes);
|
||||||
|
if (invalidFiles.length > 0) {
|
||||||
|
toast(`文件大小不能超过${maxSize}M`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = files.map((f) => ({
|
||||||
|
url: f.path,
|
||||||
|
name: f.name || '',
|
||||||
|
size: f.size
|
||||||
|
}));
|
||||||
const cur = Array.isArray(forms.value.files) ? forms.value.files : [];
|
const cur = Array.isArray(forms.value.files) ? forms.value.files : [];
|
||||||
form.files = [...cur, ...next];
|
form.files = [...cur, ...next];
|
||||||
},
|
},
|
||||||
@ -228,57 +356,68 @@ function previewFile(idx) {
|
|||||||
|
|
||||||
.upload-wrap {
|
.upload-wrap {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
margin: 10px 14px 0;
|
padding: 24rpx 30rpx;
|
||||||
border-radius: 8px;
|
border-bottom: 1px solid #eee;
|
||||||
padding: 14px;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
}
|
||||||
.upload-title {
|
.upload-row {
|
||||||
font-size: 14px;
|
display: flex;
|
||||||
font-weight: 700;
|
align-items: baseline;
|
||||||
|
margin-bottom: 18rpx;
|
||||||
|
}
|
||||||
|
.upload-label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 42rpx;
|
||||||
color: #111827;
|
color: #111827;
|
||||||
margin-bottom: 10px;
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.upload-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-left: 8rpx;
|
||||||
}
|
}
|
||||||
.upload-grid {
|
.upload-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 10px;
|
gap: 18rpx;
|
||||||
}
|
}
|
||||||
.upload-item {
|
.upload-item {
|
||||||
width: 90px;
|
width: 180rpx;
|
||||||
height: 70px;
|
height: 140rpx;
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 1px solid #d1d5db;
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
background: #f9fafb;
|
background: #f9fafb;
|
||||||
}
|
}
|
||||||
.upload-thumb {
|
.upload-thumb {
|
||||||
width: 90px;
|
width: 100%;
|
||||||
height: 70px;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.upload-remove {
|
.upload-remove {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 20px;
|
width: 36rpx;
|
||||||
height: 20px;
|
height: 36rpx;
|
||||||
line-height: 20px;
|
line-height: 36rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: rgba(0, 0, 0, 0.55);
|
background: rgba(0, 0, 0, 0.55);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 14px;
|
font-size: 28rpx;
|
||||||
}
|
}
|
||||||
.upload-add {
|
.upload-add {
|
||||||
width: 90px;
|
width: 180rpx;
|
||||||
height: 70px;
|
height: 140rpx;
|
||||||
border: 1px dashed #c7c7c7;
|
border: 1px dashed #d1d5db;
|
||||||
|
border-radius: 8rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #666;
|
color: #9ca3af;
|
||||||
}
|
}
|
||||||
.plus {
|
.plus {
|
||||||
font-size: 26px;
|
font-size: 52rpx;
|
||||||
line-height: 26px;
|
line-height: 52rpx;
|
||||||
}
|
}
|
||||||
.footer {
|
.footer {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@ -52,10 +52,13 @@
|
|||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { onLoad } from '@dcloudio/uni-app';
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { getVisitRecord, removeVisitRecord } from '@/components/archive-detail/mock';
|
import api from '@/utils/api';
|
||||||
|
import { loading, hideLoading, toast } from '@/utils/widget';
|
||||||
|
import { getVisitRecordTemplate } from '@/components/archive-detail/templates';
|
||||||
|
|
||||||
const archiveId = ref('');
|
const archiveId = ref('');
|
||||||
const id = ref('');
|
const id = ref('');
|
||||||
|
const medicalType = ref('');
|
||||||
const record = ref({});
|
const record = ref({});
|
||||||
|
|
||||||
const files = computed(() => {
|
const files = computed(() => {
|
||||||
@ -65,7 +68,38 @@ const files = computed(() => {
|
|||||||
|
|
||||||
const templateType = computed(() => record.value?.templateType || record.value?.medicalType || '');
|
const templateType = computed(() => record.value?.templateType || record.value?.medicalType || '');
|
||||||
|
|
||||||
const typeLabel = computed(() => record.value?.tempName || '病历');
|
const typeLabel = computed(() => record.value?.tempName || getVisitRecordTemplate(templateType.value || medicalType.value)?.templateName || '病历');
|
||||||
|
|
||||||
|
function normalizeText(v) {
|
||||||
|
if (Array.isArray(v)) return v.filter((i) => i !== null && i !== undefined && String(i).trim()).join(',');
|
||||||
|
if (v === 0) return '0';
|
||||||
|
return v ? String(v) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPositiveFind(v, { withOpinion = false } = {}) {
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
const list = v
|
||||||
|
.map((i) => (i && typeof i === 'object' ? { category: i.category, opinion: i.opinion } : null))
|
||||||
|
.filter((i) => i && (i.category || i.opinion));
|
||||||
|
if (!list.length) return '';
|
||||||
|
if (!withOpinion) return list.map((i) => String(i.category || '').trim()).filter(Boolean).join(':');
|
||||||
|
return list
|
||||||
|
.map((i) => {
|
||||||
|
const c = String(i.category || '').trim();
|
||||||
|
const o = String(i.opinion || '').trim();
|
||||||
|
if (c && o) return `${c}:${o}`;
|
||||||
|
return c || o;
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
return normalizeText(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCorpId() {
|
||||||
|
const team = uni.getStorageSync('ykt_case_current_team') || {};
|
||||||
|
return team?.corpId ? String(team.corpId) : '';
|
||||||
|
}
|
||||||
|
|
||||||
const visitDate = computed(() => {
|
const visitDate = computed(() => {
|
||||||
const t = templateType.value;
|
const t = templateType.value;
|
||||||
@ -78,9 +112,10 @@ const visitDate = computed(() => {
|
|||||||
|
|
||||||
const diagnosisText = computed(() => {
|
const diagnosisText = computed(() => {
|
||||||
const t = templateType.value;
|
const t = templateType.value;
|
||||||
if (t === 'preConsultation') return record.value?.chiefComplaint || record.value?.summary || '--';
|
if (t === 'preConsultation') return normalizeText(record.value?.chiefComplaint) || normalizeText(record.value?.summary) || '--';
|
||||||
if (t === 'physicalExaminationTemplate') return record.value?.positiveFind || record.value?.summary || '--';
|
if (t === 'physicalExaminationTemplate') return formatPositiveFind(record.value?.positiveFind) || normalizeText(record.value?.summary) || '--';
|
||||||
return record.value?.diagnosisName || record.value?.summary || '--';
|
if (t === 'outpatient' || t === 'inhospital') return normalizeText(record.value?.diagnosisName || record.value?.diagnosis) || normalizeText(record.value?.summary) || '--';
|
||||||
|
return normalizeText(record.value?.diagnosisName || record.value?.diagnosis || record.value?.summary) || '--';
|
||||||
});
|
});
|
||||||
|
|
||||||
const sections = computed(() => {
|
const sections = computed(() => {
|
||||||
@ -118,7 +153,7 @@ const sections = computed(() => {
|
|||||||
if (t === 'physicalExaminationTemplate') {
|
if (t === 'physicalExaminationTemplate') {
|
||||||
const corp = push('体检机构', record.value?.corpName);
|
const corp = push('体检机构', record.value?.corpName);
|
||||||
const pkg = push('体检套餐', record.value?.inspectPakageName);
|
const pkg = push('体检套餐', record.value?.inspectPakageName);
|
||||||
const positive = push('阳性发现', record.value?.positiveFind);
|
const positive = push('阳性发现及处理意见', formatPositiveFind(record.value?.positiveFind, { withOpinion: true }));
|
||||||
const summary = push('摘要', record.value?.summary);
|
const summary = push('摘要', record.value?.summary);
|
||||||
[corp, pkg, positive, summary].forEach((i) => i && list.push(i));
|
[corp, pkg, positive, summary].forEach((i) => i && list.push(i));
|
||||||
return list;
|
return list;
|
||||||
@ -130,26 +165,61 @@ const sections = computed(() => {
|
|||||||
|
|
||||||
const topText = computed(() => {
|
const topText = computed(() => {
|
||||||
const time = record.value?.createTime ? dayjs(record.value.createTime).format('YYYY-MM-DD HH:mm') : '';
|
const time = record.value?.createTime ? dayjs(record.value.createTime).format('YYYY-MM-DD HH:mm') : '';
|
||||||
const name = record.value?.creatorName ? String(record.value.creatorName) : '';
|
const rawName = record.value?.creatorName ? String(record.value.creatorName) : '';
|
||||||
return `${time || '--'} ${name || '--'}创建`;
|
const cleanName = ['-', '—', '--'].includes(rawName.trim()) ? '' : rawName.trim();
|
||||||
|
const byCustomer = record.value?.ignore === 'checkIn';
|
||||||
|
const suffix = byCustomer ? '患者自建' : cleanName ? `${cleanName}代建` : record.value?.creator ? '员工代建' : '';
|
||||||
|
return suffix ? `${time || '--'} ${suffix}` : `${time || '--'}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
onLoad((opt) => {
|
onLoad(async (opt) => {
|
||||||
archiveId.value = opt?.archiveId ? String(opt.archiveId) : '';
|
archiveId.value = opt?.archiveId ? String(opt.archiveId) : '';
|
||||||
id.value = opt?.id ? String(opt.id) : '';
|
id.value = opt?.id ? String(opt.id) : '';
|
||||||
if (!archiveId.value || !id.value) {
|
medicalType.value = opt?.type ? String(opt.type) : '';
|
||||||
uni.showToast({ title: '参数缺失', icon: 'none' });
|
if (!archiveId.value || !id.value || !medicalType.value) {
|
||||||
|
toast('参数缺失');
|
||||||
setTimeout(() => uni.navigateBack(), 300);
|
setTimeout(() => uni.navigateBack(), 300);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const r = getVisitRecord({ archiveId: archiveId.value, id: id.value });
|
|
||||||
if (!r) {
|
// 使用真实 API 获取病历记录
|
||||||
uni.showToast({ title: '记录不存在', icon: 'none' });
|
loading('加载中...');
|
||||||
|
try {
|
||||||
|
const corpId = getCorpId();
|
||||||
|
if (!corpId) {
|
||||||
|
hideLoading();
|
||||||
|
toast('缺少 corpId');
|
||||||
setTimeout(() => uni.navigateBack(), 300);
|
setTimeout(() => uni.navigateBack(), 300);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const res = await api('getMedicalRecordById', {
|
||||||
|
_id: id.value,
|
||||||
|
corpId,
|
||||||
|
memberId: archiveId.value,
|
||||||
|
medicalType: medicalType.value,
|
||||||
|
});
|
||||||
|
hideLoading();
|
||||||
|
const r = res?.record || res?.data?.record || null;
|
||||||
|
if (!res?.success || !r) {
|
||||||
|
toast('记录不存在');
|
||||||
|
setTimeout(() => uni.navigateBack(), 300);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容模板字段:wxapp 使用 diagnosis,但接口通常返回 diagnosisName
|
||||||
|
if ((r.medicalType === 'outpatient' || r.medicalType === 'inhospital') && !r.diagnosis && r.diagnosisName) {
|
||||||
|
r.diagnosis = r.diagnosisName;
|
||||||
|
}
|
||||||
|
|
||||||
record.value = r;
|
record.value = r;
|
||||||
uni.setNavigationBarTitle({ title: r?.tempName ? String(r.tempName) : '病历详情' });
|
uni.setNavigationBarTitle({ title: String(typeLabel.value || '病历详情') });
|
||||||
|
} catch (error) {
|
||||||
|
hideLoading();
|
||||||
|
console.error('获取病历记录失败:', error);
|
||||||
|
toast('加载失败');
|
||||||
|
setTimeout(() => uni.navigateBack(), 300);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function preview(idx) {
|
function preview(idx) {
|
||||||
@ -168,12 +238,26 @@ function remove() {
|
|||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
content: '确定删除当前记录?',
|
content: '确定删除当前记录?',
|
||||||
success: (res) => {
|
success: async (res) => {
|
||||||
if (!res.confirm) return;
|
if (!res.confirm) return;
|
||||||
removeVisitRecord({ archiveId: archiveId.value, id: id.value });
|
|
||||||
|
loading('删除中...');
|
||||||
|
try {
|
||||||
|
const corpId = getCorpId();
|
||||||
|
if (!corpId) {
|
||||||
|
hideLoading();
|
||||||
|
return toast('缺少 corpId');
|
||||||
|
}
|
||||||
|
await api('removeMedicalRecord', { corpId, memberId: archiveId.value, medicalType: medicalType.value, _id: id.value });
|
||||||
|
hideLoading();
|
||||||
uni.$emit('archive-detail:visit-record-changed');
|
uni.$emit('archive-detail:visit-record-changed');
|
||||||
uni.showToast({ title: '已删除', icon: 'success' });
|
toast('已删除');
|
||||||
setTimeout(() => uni.navigateBack(), 300);
|
setTimeout(() => uni.navigateBack(), 300);
|
||||||
|
} catch (error) {
|
||||||
|
hideLoading();
|
||||||
|
console.error('删除病历记录失败:', error);
|
||||||
|
toast('删除失败');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
185
pages/library/diagnosis-list.vue
Normal file
185
pages/library/diagnosis-list.vue
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<view class="top">
|
||||||
|
<uni-easyinput v-model="keyword" prefixIcon="search" placeholder="请搜索诊断名称" @input="onInput" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<scroll-view scroll-y class="scroll">
|
||||||
|
<view v-for="item in showList" :key="item.key" class="row" @click="toggle(item)">
|
||||||
|
<view class="label">{{ item.label }}</view>
|
||||||
|
<uni-icons :type="selectedMap[item.label] ? 'checkmarkempty' : ''" size="22" color="#007aff" />
|
||||||
|
</view>
|
||||||
|
<view v-if="showList.length === 0" class="empty">暂无诊断数据</view>
|
||||||
|
<view style="height: 120px;"></view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<view class="footer">
|
||||||
|
<button class="btn primary" @click="save">确定</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onUnmounted, ref } from 'vue';
|
||||||
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
|
import api from '@/utils/api';
|
||||||
|
import { toast } from '@/utils/widget';
|
||||||
|
|
||||||
|
const ready = ref(false);
|
||||||
|
const keyword = ref('');
|
||||||
|
const list = ref([]);
|
||||||
|
const selections = ref([]);
|
||||||
|
const mult = ref(false);
|
||||||
|
const eventName = ref('change-diagnosis');
|
||||||
|
|
||||||
|
const selectedMap = computed(() => selections.value.reduce((m, i) => ((m[i] = true), m), {}));
|
||||||
|
|
||||||
|
function normalizeText(v) {
|
||||||
|
return v ? String(v) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullMatched = computed(() => {
|
||||||
|
const value = String(keyword.value || '').trim();
|
||||||
|
if (!value) return null;
|
||||||
|
return { label: value, value, key: `full_${value}` };
|
||||||
|
});
|
||||||
|
|
||||||
|
const showList = computed(() => {
|
||||||
|
const base = Array.isArray(list.value) ? list.value : [];
|
||||||
|
const arr = [];
|
||||||
|
if (fullMatched.value) arr.push(fullMatched.value);
|
||||||
|
base.forEach((i) => {
|
||||||
|
if (!i || !i.label) return;
|
||||||
|
if (fullMatched.value && i.label === fullMatched.value.label) return;
|
||||||
|
arr.push(i);
|
||||||
|
});
|
||||||
|
return arr;
|
||||||
|
});
|
||||||
|
|
||||||
|
let timer = null;
|
||||||
|
function onInput() {
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
timer = null;
|
||||||
|
query();
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
});
|
||||||
|
|
||||||
|
onLoad((opt) => {
|
||||||
|
eventName.value = opt?.eventName ? String(opt.eventName) : 'change-diagnosis';
|
||||||
|
mult.value = String(opt?.mult || '') === 'YES';
|
||||||
|
|
||||||
|
if (mult.value) {
|
||||||
|
const data = uni.getStorageSync('diagnosis-list-selection');
|
||||||
|
selections.value = Array.isArray(data) ? data : [];
|
||||||
|
uni.removeStorageSync('diagnosis-list-selection');
|
||||||
|
} else {
|
||||||
|
const v = opt?.value ? String(opt.value) : '';
|
||||||
|
selections.value = v ? [v] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
ready.value = true;
|
||||||
|
query();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function query() {
|
||||||
|
if (!ready.value) return;
|
||||||
|
const value = String(keyword.value || '').trim();
|
||||||
|
|
||||||
|
uni.showLoading({ title: '加载中...' });
|
||||||
|
try {
|
||||||
|
const res = await api('getDisease', { diseaseName: value });
|
||||||
|
const arr = Array.isArray(res?.data?.data) ? res.data.data : [];
|
||||||
|
list.value = arr
|
||||||
|
.map((i) => {
|
||||||
|
const label = normalizeText(i?.diseaseName);
|
||||||
|
const code = normalizeText(i?.code);
|
||||||
|
if (!label) return null;
|
||||||
|
return { label, value: code || label, key: code || label };
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
} catch (e) {
|
||||||
|
list.value = [];
|
||||||
|
} finally {
|
||||||
|
uni.hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle(item) {
|
||||||
|
if (!item || !item.label) return;
|
||||||
|
const index = selections.value.findIndex((i) => i === item.label);
|
||||||
|
if (index >= 0) {
|
||||||
|
selections.value.splice(index, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mult.value) selections.value.push(item.label);
|
||||||
|
else selections.value = [item.label];
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
if (selections.value.length === 0) return toast('请选择');
|
||||||
|
if (mult.value) uni.$emit(eventName.value, selections.value);
|
||||||
|
else uni.$emit(eventName.value, selections.value[0]);
|
||||||
|
uni.navigateBack();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #fff;
|
||||||
|
padding-bottom: calc(76px + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
.top {
|
||||||
|
padding: 12px 14px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.scroll {
|
||||||
|
height: calc(100vh - 140px);
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 14px;
|
||||||
|
border-bottom: 1px solid #f2f2f2;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #111827;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.empty {
|
||||||
|
padding: 60px 0;
|
||||||
|
text-align: center;
|
||||||
|
color: #9aa0a6;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
|
||||||
|
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
.btn::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.btn.primary {
|
||||||
|
background: #4f6ef7;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
128
pages/others/edit-positive-find.vue
Normal file
128
pages/others/edit-positive-find.vue
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<scroll-view scroll-y class="scroll">
|
||||||
|
<view class="section">
|
||||||
|
<view class="title">阳性发现</view>
|
||||||
|
<textarea
|
||||||
|
v-model="category"
|
||||||
|
class="textarea"
|
||||||
|
placeholder="请输入阳性发现"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
:maxlength="200"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="section">
|
||||||
|
<view class="title">处理意见</view>
|
||||||
|
<textarea
|
||||||
|
v-model="opinion"
|
||||||
|
class="textarea"
|
||||||
|
placeholder="请输入处理意见"
|
||||||
|
placeholder-class="placeholder"
|
||||||
|
:maxlength="500"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view style="height: 120px;"></view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<view class="footer">
|
||||||
|
<button class="btn plain" @click="cancel">取消</button>
|
||||||
|
<button class="btn primary" @click="save">保存</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { toast } from '@/utils/widget';
|
||||||
|
|
||||||
|
const category = ref('');
|
||||||
|
const opinion = ref('');
|
||||||
|
const eventName = ref('');
|
||||||
|
|
||||||
|
onLoad((opt) => {
|
||||||
|
eventName.value = String(opt?.eventName || '');
|
||||||
|
if (opt?.title) uni.setNavigationBarTitle({ title: String(opt.title) });
|
||||||
|
const data = uni.getStorageSync('current-positive-find') || {};
|
||||||
|
category.value = typeof data?.category === 'string' ? data.category : '';
|
||||||
|
opinion.value = typeof data?.opinion === 'string' ? data.opinion : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
function cancel() {
|
||||||
|
uni.navigateBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
if (!String(category.value || '').trim()) return toast('请输入阳性发现');
|
||||||
|
if (!String(opinion.value || '').trim()) return toast('请输入处理意见');
|
||||||
|
if (eventName.value) {
|
||||||
|
uni.$emit(eventName.value, { category: String(category.value).trim(), opinion: String(opinion.value).trim() });
|
||||||
|
}
|
||||||
|
uni.navigateBack();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #fff;
|
||||||
|
padding-bottom: calc(76px + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
.scroll {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
padding: 16px 14px 0;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111827;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 120px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
.placeholder {
|
||||||
|
color: #9aa0a6;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 14px 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: #4f6ef7;
|
||||||
|
border: 1px solid #4f6ef7;
|
||||||
|
}
|
||||||
|
.btn.primary {
|
||||||
|
background: #4f6ef7;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -93,6 +93,14 @@ export default [
|
|||||||
path: 'pages/case/plan-execute',
|
path: 'pages/case/plan-execute',
|
||||||
meta: { title: '执行回访计划', login: false },
|
meta: { title: '执行回访计划', login: false },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'pages/library/diagnosis-list',
|
||||||
|
meta: { title: '诊断', login: false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'pages/others/edit-positive-find',
|
||||||
|
meta: { title: '阳性发现', login: false },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'pages/work/work',
|
path: 'pages/work/work',
|
||||||
meta: { title: '工作台', login: false }
|
meta: { title: '工作台', login: false }
|
||||||
|
|||||||
10
utils/api.js
10
utils/api.js
@ -16,7 +16,9 @@ const urlsConfig = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
knowledgeBase: {
|
knowledgeBase: {
|
||||||
getArticleByIds: 'getArticleByIds'
|
getArticleByIds: 'getArticleByIds',
|
||||||
|
// 诊断库(对齐 ykt-management-mobile/src/api/knowledgeBase.js)
|
||||||
|
getDisease: 'getDisease',
|
||||||
},
|
},
|
||||||
member: {
|
member: {
|
||||||
addCustomer: 'add',
|
addCustomer: 'add',
|
||||||
@ -36,6 +38,12 @@ const urlsConfig = {
|
|||||||
searchCorpCustomer: 'searchCorpCustomer',
|
searchCorpCustomer: 'searchCorpCustomer',
|
||||||
searchCorpCustomerWithFollowTime: 'searchCorpCustomerWithFollowTime',
|
searchCorpCustomerWithFollowTime: 'searchCorpCustomerWithFollowTime',
|
||||||
unbindMiniAppArchive: 'unbindMiniAppArchive',
|
unbindMiniAppArchive: 'unbindMiniAppArchive',
|
||||||
|
// 健康档案相关接口(对齐 ykt-management-mobile/src/api/member.js)
|
||||||
|
addMedicalRecord: 'addMedicalRecord',
|
||||||
|
getMedicalRecordById: 'getMedicalRecordById',
|
||||||
|
updateMedicalRecord: 'updateMedicalRecord',
|
||||||
|
removeMedicalRecord: 'removeMedicalRecord',
|
||||||
|
getCustomerMedicalRecord: 'getCustomerMedicalRecord',
|
||||||
},
|
},
|
||||||
wecom: {
|
wecom: {
|
||||||
addContactWay: 'addContactWay'
|
addContactWay: 'addContactWay'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user