hn-hlw-app/pages/consult/disease-description.vue
2026-08-13 13:50:42 +08:00

661 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<full-page ref="fullPageRef" mainStyle="background:#f0f0f0">
<view class="container" :class="{ 'elder-mode-active': elderMode }" @click="handlePageClick">
<view class="user-header shadow-lg">
就诊人{{ form.name }} {{ form.sex }} {{ form.age ? `${form.age}` : '' }}
</view>
<view v-if="medTypeList.length > 1" class="med-type-section relative mt-12 px-15 py-12 bg-white shadow-lg">
<view class="med-type-row">
<view class="section-title med-type-title is-required">本次就诊类型</view>
<radio-group class="med-type-options">
<label v-for="item in medTypeList" :key="getMedTypeKey(item)" class="med-type-option">
<radio :value="getMedTypeKey(item)" :checked="isMedTypeSelected(item)" @change="changeMedType(item)"
class="custom-radio" color="#b8956a" />
<text>{{ item.diseType }}</text>
</label>
</radio-group>
</view>
</view>
<view id="description-section" class="description-section relative mt-12 px-15 py-12 bg-white shadow-lg">
<view class="section-title is-required">请描述您的病情</view>
<textarea v-model="form.description" :show-confirm-bar="false" auto-height="true" class="mt-10 description"
placeholder-class="placeholderClass" maxlength="300" placeholder="为了更好地获得医生帮助,请尽可能地详细描述病情"
:adjust-position="false"
@click.stop
@focus="showDescriptionGuess" @input="handleDescriptionInput" @blur="hideDescriptionGuess"
@keyboardheightchange="handleKeyboardHeightChange"></textarea>
<view v-if="guessVisible" id="description-guess-anchor" class="relative">
<scroll-view class="guess-popup" scroll-y :style="{ height: `${guessPopupHeight}px` }"
@click.stop
@touchstart="handleGuessInteractionStart" @touchend="handleGuessInteractionEnd"
@touchcancel="handleGuessInteractionEnd" @mousedown="handleGuessInteractionStart"
@mouseup="handleGuessInteractionEnd">
<view id="description-guess-content">
<view class="guess-title">猜你想输入</view>
<view v-for="item in guessList" :key="item" class="guess-option" @click="selectDescriptionGuess(item)">
{{ item }}
</view>
</view>
</scroll-view>
</view>
</view>
<view class="relative mt-12 px-15 py-12 bg-white shadow-lg">
<view class="section-title">上传本院或者外院的病历资料</view>
<view class="mt-10 section-sub-title">
请上传诊断处方出院小结医嘱单浙里办医保结算明细等其中一种
</view>
<view class="image-uploader">
<view v-for="(image, index) in form.images" :key="index" class="image-preview">
<image :src="image" class="image" @click="previewImage(index)" />
<view class="close-icon" @click="removeImage(index)">
<uni-icons color="red" size="24" type="close" />
</view>
</view>
<view v-if="form.images.length < 9" class="upload-button" @click="uploadImage">
<uni-icons type="camera" size="40" />
</view>
</view>
</view>
<view class="relative mt-12 px-15 py-12 bg-white shadow-lg">
<view v-for="history in histories" :key="history.label" class="history-item">
<text class="history-label">{{ history.label }}</text>
<radio-group class="radio-group">
<label v-for="i in history.options" :key="i" class="radio-label">
<radio :value="i" :checked="form[history.prop] === i" @change="changeHistory(history.prop, i)"
class="custom-radio" :color="themeColor" />
<text>{{ i }}</text>
</label>
</radio-group>
</view>
</view>
</view>
<template #footer>
<footer-button text="下一步" theme="brown" @onClick="nextStep" />
</template>
</full-page>
</template>
<script setup>
import { getCurrentInstance, nextTick, ref } from "vue";
import { storeToRefs } from "pinia";
import { onLoad, onUnload } from '@dcloudio/uni-app';
import orderStore from '@/store/order';
import { get, remove } from "@/utils/cache";
import { uploadUrl, getFullPath } from '@/utils/http';
import { toast, loading, hideLoading } from '@/utils/widget';
import { themeBg, themeColor, themeSecondary } from "@/utils/theme-config";
import footerButton from '@/components/footer-button.vue';
import fullPage from '@/components/full-page.vue';
const guessList = [
'药已用完,需要再次开药',
'病情稳定,要求开具处方买药',
'没有药了,要求开放续药',
'家里药已用完,需要开具新的处方',
'药用完了,需要再开一些', '病情稳定,要求开方配药',
'上次开的药已经用完,需要续药',
'储备的药品已用完,要求开方配药'
]
const { order: form, histories, medTypeList } = storeToRefs(orderStore());
const { init: initOrder, restoreForEdit } = orderStore();
const visible = ref(false);
const fullPageRef = ref(null);
const guessVisible = ref(false);
const guessPopupHeight = ref(320);
const reachBottom = ref(false);
let guessCloseTimer = null;
let guessResizeTimer = null;
let guessInteractionTimer = null;
let keyboardHeight = 0;
let isGuessInteracting = false;
const instance = getCurrentInstance();
// 长辈模式状态
const elderMode = ref(false);
const titleMap = {
YB: "医保处方",
ZF: "自费处方",
};
function changeHistory(prop, value) {
form.value[prop] = value;
}
function getMedTypeKey(item) {
return [item.med_type, item.diseType, item.dise_codg, item.dise_name].join('-');
}
function isMedTypeSelected(item) {
const medInfo = form.value.medInfo || {};
return getMedTypeKey(medInfo) === getMedTypeKey(item);
}
function changeMedType(item) {
if (!isMedTypeSelected(item)) {
form.value.medInfo = { ...item };
}
}
function showDescriptionGuess() {
if (guessCloseTimer) clearTimeout(guessCloseTimer);
guessCloseTimer = null;
guessVisible.value = !form.value.description.trim();
if (fullPageRef.value) fullPageRef.value.scrollToView('description-section');
if (guessVisible.value) resizeGuessPopup();
}
function resizeGuessPopup(delay = 0) {
if (guessResizeTimer) clearTimeout(guessResizeTimer);
guessResizeTimer = setTimeout(async () => {
await nextTick();
const systemInfo = uni.getSystemInfoSync();
const defaultHeight = Math.round(systemInfo.windowWidth / 750 * 640);
const query = uni.createSelectorQuery();
if (instance && instance.proxy) query.in(instance.proxy);
query.select('#description-guess-anchor').boundingClientRect();
query.select('#description-guess-content').boundingClientRect();
query.exec((rects) => {
if (!guessVisible.value || !rects || !rects[0]) return;
const anchorRect = rects[0];
const contentRect = rects[1];
const keyboardTop = systemInfo.windowHeight - keyboardHeight;
const availableHeight = Math.max(96, keyboardTop - anchorRect.top - 12);
const contentHeight = contentRect && contentRect.height ? contentRect.height : defaultHeight;
guessPopupHeight.value = Math.ceil(Math.min(defaultHeight, contentHeight, availableHeight));
});
guessResizeTimer = null;
}, delay);
}
function handleKeyboardHeightChange(event) {
keyboardHeight = event && event.detail ? Number(event.detail.height) || 0 : 0;
if (guessVisible.value) resizeGuessPopup(50);
}
function handleDescriptionInput(event) {
const description = event && event.detail && typeof event.detail.value === 'string'
? event.detail.value
: form.value.description;
if (description.trim()) {
guessVisible.value = false;
}
}
function hideDescriptionGuess() {
if (guessCloseTimer) clearTimeout(guessCloseTimer);
guessCloseTimer = setTimeout(() => {
if (isGuessInteracting) {
guessCloseTimer = null;
return;
}
guessVisible.value = false;
guessCloseTimer = null;
}, 200);
}
function closeDescriptionGuess() {
if (guessCloseTimer) clearTimeout(guessCloseTimer);
if (guessInteractionTimer) clearTimeout(guessInteractionTimer);
guessCloseTimer = null;
guessInteractionTimer = null;
isGuessInteracting = false;
guessVisible.value = false;
}
function handlePageClick() {
if (guessVisible.value) closeDescriptionGuess();
}
function handleGuessInteractionStart() {
isGuessInteracting = true;
if (guessInteractionTimer) clearTimeout(guessInteractionTimer);
if (guessCloseTimer) clearTimeout(guessCloseTimer);
guessCloseTimer = null;
// 部分小程序的 scroll-view 不触发 touchend设置兜底避免交互状态一直保留。
guessInteractionTimer = setTimeout(() => {
isGuessInteracting = false;
guessInteractionTimer = null;
}, 500);
}
function handleGuessInteractionEnd() {
if (guessInteractionTimer) clearTimeout(guessInteractionTimer);
guessInteractionTimer = setTimeout(() => {
isGuessInteracting = false;
guessInteractionTimer = null;
}, 300);
}
function selectDescriptionGuess(description) {
form.value.description = description;
closeDescriptionGuess();
}
function nextStep() {
if (form.value.description.trim() === '') {
toast('请填写病情描述');
return;
}
for (let item of histories.value) {
if (!item.options.includes(form.value[item.prop])) {
toast(`请选择${item.label}`);
return;
}
}
if (form.value.hasVisitedInLastSixMonths === 'N') {
confirmVisible.value = true;
return;
}
uni.navigateTo({ url: '/pages/consult/info' })
}
function onDiseaseSelected(disease) {
if (form.value.diseases.length < 5) {
form.value.diseases.push(disease);
}
}
function removeImage(index) {
form.value.images.splice(index, 1);
}
function uploadImage() {
uni.chooseImage({
count: 9 - form.value.images.length,
sizeType: ['compressed'],
success: async (res) => {
loading('上传中...');
const arr = await Promise.all(res.tempFilePaths.map(upload));
const urls = arr.filter(Boolean);
form.value.images = [...form.value.images, ...urls];
hideLoading();
toast(`成功上传${urls.length}张图片`);
},
});
}
function previewImage(index) {
uni.previewImage({
urls: form.value.images,
current: index
})
}
function upload(path) {
return new Promise((resolve) => {
uni.uploadFile({
url: uploadUrl, // 替换为你的上传接口地址
filePath: path,
name: 'file',
fileType: 'image',
success: (res) => {
try {
const url = JSON.parse(res.data).filePath;
resolve(url ? getFullPath(url) : '')
} catch (e) {
resolve()
}
},
fail: res => {
resolve()
}
})
})
}
onLoad((options) => {
if (options.reEdit === '1') {
const record = get('re-edit-consult-order');
remove('re-edit-consult-order');
if (!record || record.orderId !== options.orderId || !restoreForEdit(record)) {
toast('问诊资料已失效,请重新进入聊天室操作');
uni.navigateBack();
return;
}
} else {
const hisArchive = get("current-his-archive");
remove("current-his-archive");
initOrder({ feeType: options.feeType, socialno: options.socialno, hisArchive });
}
uni.setNavigationBarTitle({
title: titleMap[options.feeType] || ''
})
uni.$on('on-disease-selected', onDiseaseSelected);
})
onUnload(() => {
uni.$off('on-disease-selected')
if (guessCloseTimer) clearTimeout(guessCloseTimer);
if (guessResizeTimer) clearTimeout(guessResizeTimer);
if (guessInteractionTimer) clearTimeout(guessInteractionTimer);
})
</script>
<style lang="scss" scoped>
.section-title {
font-weight: bold;
font-size: 32rpx;
&.is-required:before {
display: inline-block;
content: "*";
color: red;
margin-right: 10rpx;
}
}
.title-font {
font-size: 32rpx;
}
.med-type-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.med-type-title {
flex-shrink: 0;
}
.med-type-options {
display: flex;
align-items: center;
justify-content: flex-end;
flex: 1;
}
.med-type-option {
display: flex;
align-items: center;
margin-left: 24rpx;
font-size: 28rpx;
white-space: nowrap;
}
.section-sub-title {
font-size: 28rpx;
color: #555;
line-height: 42rpx;
}
.disease-tags {
display: flex;
flex-wrap: wrap;
margin-right: -20rpx;
}
.disease-tag {
padding: 10rpx 20rpx;
background-color: #f5ede0;
border-radius: 16rpx;
margin-right: 20rpx;
margin-top: 20rpx;
font-size: 28rpx;
color: #8b6f47;
border: 1px solid #d4c4a8;
@at-root &.add-tag {
border-color: #b8956a;
background-color: white;
color: #b8956a;
}
}
.disease-remove {
display: inline-block;
padding: 0 16rpx;
}
.image-uploader {
display: flex;
flex-wrap: wrap;
}
.item-guess {
font-size: 28rpx;
font-weight: bold;
}
.image-preview,
.upload-button {
position: relative;
width: 140rpx;
height: 140rpx;
margin: 10rpx 10rpx 0 0;
display: flex;
align-items: center;
justify-content: center;
}
.upload-button {
border: 1px solid #e5e5e5;
}
.close-icon {
position: absolute;
top: -20rpx;
right: -20rpx;
width: 40rpx;
height: 40rpx;
display: flex;
justify-content: flex-end;
}
.image {
width: 140rpx;
height: 140rpx;
}
.next-button {
background-color: $theme-brown-primary;
color: white;
position: fixed;
bottom: 20rpx;
/* width: 100%; */
left: 40rpx;
right: 40rpx;
}
.history-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.history-item-none {
padding: 0;
}
.history-item:last-child {
border-bottom: none;
}
.history-label {
width: 280rpx;
font-size: 14px;
color: #555;
}
.radio-group {
display: flex;
align-items: center;
justify-content: space-between;
width: 320rpx;
}
.radio-group-auto {
width: auto;
}
.radio-label {
width: 50%;
display: flex;
align-items: center;
margin-left: 15px;
font-size: 28rpx;
white-space: nowrap;
}
.custom-radio {
transform: scale(0.8);
}
.description {
width: 100%;
min-height: 84rpx;
}
.description-section {
z-index: 10;
}
.guess-popup {
position: absolute;
left: 30rpx;
right: 30rpx;
top: 0;
z-index: 20;
box-sizing: border-box;
background: #fff;
border: 1rpx solid #d9d9d9;
box-shadow: 6rpx 6rpx 12rpx rgba(0, 0, 0, 0.25);
}
.guess-title,
.guess-option {
box-sizing: border-box;
min-height: 64rpx;
padding: 16rpx 20rpx;
border-bottom: 1rpx solid #eee;
font-size: 28rpx;
color: #333;
}
.guess-title {
font-weight: bold;
}
.guess-option:last-child {
border-bottom: none;
}
.guess-option:active {
background: #f5f5f5;
}
.placeholderClass {
font-size: 28rpx;
}
.user-header {
padding: 24rpx 30rpx;
background: white;
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.flex-row {
display: flex;
align-items: center;
}
/* 长辈模式特定样式 */
.elder-mode-active {
.user-header {
font-size: 42rpx !important;
padding: 40rpx 30rpx !important;
}
.section-title {
font-size: 42rpx !important;
margin-bottom: 30rpx;
}
.title-font {
font-size: 42rpx !important;
}
.med-type-row {
align-items: flex-start;
flex-direction: column;
}
.med-type-options {
width: 100%;
justify-content: space-between;
margin-top: 20rpx;
}
.med-type-option {
margin-left: 0;
font-size: 36rpx !important;
}
.section-sub-title {
font-size: 36rpx !important;
line-height: 1.6 !important;
}
.disease-tag {
font-size: 36rpx !important;
padding: 15rpx 25rpx !important;
margin-right: 25rpx !important;
margin-bottom: 25rpx !important;
}
.description {
font-size: 36rpx !important;
min-height: 200rpx !important;
padding: 20rpx !important;
line-height: 1.6 !important;
box-sizing: border-box !important;
}
.guess-title,
.guess-option {
min-height: 84rpx;
padding: 20rpx 24rpx;
font-size: 34rpx;
line-height: 44rpx;
}
.placeholderClass {
font-size: 36rpx !important;
line-height: 1.6 !important;
}
.history-label {
font-size: 36rpx !important;
width: 320rpx !important;
}
.radio-label {
font-size: 36rpx !important;
margin-left: 20rpx !important;
}
.image-preview,
.upload-button {
width: 200rpx !important;
height: 200rpx !important;
margin: 10rpx !important;
}
.image {
width: 200rpx !important;
height: 200rpx !important;
}
}
</style>