| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668 |
- <script setup lang="ts">
- import type { TaskModel } from '@/model/follow.model';
- import { FillFollowContentMethod, UploadIFile, FollowContentMethod } from '@/request/api/follow.api';
- import { getDictionaryMethod } from '@/request/api/dictionary.api';
- import { useRequest } from 'alova/client';
- import { PlusOutlined } from '@ant-design/icons-vue';
- import { notification } from 'ant-design-vue';
- import type { UploadFile } from 'ant-design-vue/es/upload/interface';
- import { VxeUI } from 'vxe-pc-ui';
- type FormModel = Partial<TaskModel>;
- const props = defineProps<{ data: FormModel }>();
- const emits = defineEmits<{
- submit: [data?: TaskModel];
- }>();
- const {
- data: contentArr,
- loading,
- send: load,
- } = useRequest(() => FollowContentMethod(props.data), {
- initialData: [],
- }).onSuccess(({ data }) => {
- const index = data.findIndex((item) => item.id === props.data.id);
- if (index > -1) {
- changeTab(data[index], index);
- }
- });
- const statusList = ref<string[]>([]);
- onBeforeMount(() => {
- getDictionaryMethod('followup_syndrome_change').then((res) => {
- statusList.value = res;
- });
- });
- const activeKey = ref<number>();
- const activeIndex = ref<number>();
- const activeObj = ref<any>({ fillin: {}, symptomsData: [] });
- // 过期判定与只读
- const isExpired = computed(() => {
- const t = activeObj.value?.arrangeTime;
- if (!t) return false;
- const arrange = new Date(t as any);
- const ts = arrange.getTime();
- console.log(ts,"获取此数据",t,Date.now());
- if (Number.isNaN(ts)) return false;
- const arrangeDay = new Date(arrange);
- arrangeDay.setHours(0, 0, 0, 0);
- const today = new Date();
- today.setHours(0, 0, 0, 0);
- return arrangeDay.getTime() < today.getTime();
- });
- const isReadOnly = computed(() => isExpired.value || activeObj.value?.progress === '2' || activeObj.value?.progress === '0');
- // 切换侧边栏任务
- const changeTab = (data: any, index: number) => {
- activeKey.value = data.id;
- activeObj.value = { ...data, fillin: { ...data.fillin } };
- activeIndex.value = index;
- activeObj.value.symptomsData = [];
- downImageList.value = [];
- const upImg = data.fillin?.upImg;
- const downImg = data.fillin?.downImg;
- const faceImg = data.fillin?.faceImg;
- if (activeObj.value?.syndromeList && activeObj.value.syndromeList.length > 0) {
- activeObj.value.syndromeList.forEach((syndrome) => {
- activeObj.value.symptomsData.push({ name: syndrome });
- });
- activeObj.value.symptomsData.forEach((item) => {
- item.child = statusList.value;
- item.selectedValue = '';
- item.selectedId = null;
- item.id = data.id;
- });
- }
- symptomsValue.value.parent = '';
- upImgList.value = upImg
- ? [
- {
- uid: '-1',
- status: 'done',
- url: upImg,
- thumbUrl: upImg,
- response: { url: upImg },
- },
- ]
- : [];
- downImageList.value = downImg
- ? [
- {
- uid: '-1',
- status: 'done',
- url: downImg,
- thumbUrl: downImg,
- response: { url: downImg },
- },
- ]
- : [];
- faceImageList.value = faceImg
- ? [
- {
- uid: '-1',
- status: 'done',
- url: faceImg,
- thumbUrl: faceImg,
- response: { url: faceImg },
- },
- ]
- : [];
- uploadProps.showRemoveIcon = data.progress === '1';
- };
- // 存储所有选择的症状
- const selectedSymptomsList = ref<{ name: string; value: string }[]>([]);
- // 存储症状
- const symptomsList = ref<{ name: string; type: string }[]>([]);
- // 症状选择的值
- const symptomsValue = ref({
- parent: '',
- child: '',
- });
- // 处理父级点击
- const handleParentClick = (name: string) => {
- if (symptomsValue.value.parent === name) {
- // 如果点击的是当前选中的父级,则清空选择
- symptomsValue.value = {
- parent: '',
- child: '',
- };
- } else {
- // 选择新的父级
- symptomsValue.value = {
- parent: name,
- child: '',
- };
- }
- };
- // 处理子级选择变化
- const handleChildChange = (e: any) => {
- const selectedValue = e.target.value;
- const currentParent = symptomsValue.value.parent;
- // 找到当前症状
- const symptom = activeObj.value.symptomsData.find((item) => item.name === currentParent);
- if (symptom) {
- // 如果点击的是当前选中的值,则取消选择
- if (symptom.selectedId === selectedValue) {
- // 取消选择
- symptom.selectedValue = '';
- symptom.selectedId = null;
- // 从已选择的症状列表中移除
- const index = selectedSymptomsList.value.findIndex((item) => item.name === currentParent);
- if (index > -1) {
- selectedSymptomsList.value.splice(index, 1);
- }
- } else {
- // 选择新的值
- const child = symptom.child.find((item) => item.value === selectedValue);
- if (child) {
- symptom.selectedValue = selectedValue;
- symptom.selectedId = selectedValue;
- // 更新已选择的症状列表
- const existingIndex = selectedSymptomsList.value.findIndex((item) => item.name === currentParent);
- if (existingIndex > -1) {
- selectedSymptomsList.value[existingIndex].value = selectedValue;
- } else {
- selectedSymptomsList.value.push({
- name: currentParent,
- value: selectedValue,
- });
- }
- }
- }
- }
- symptomsValue.value.parent = '';
- };
- // 是否出现新症状
- const selectSymptomsData = reactive([
- { name: '有', id: 'Y' },
- { name: '没有', id: 'N' },
- ]);
- const uploadProps = reactive({ showRemoveIcon: true });
- const changeTag = (item: any) => {
- activeObj.value.fillin.isHaveNewSyndrome = item.id;
- };
- const upImgList = ref<UploadFile[]>([]);
- const downImageList = ref<UploadFile[]>([]);
- const faceImageList = ref<UploadFile[]>([]);
- // 预览图片
- const handlePreview = async (file: UploadFile) => {
- previewImg.value = file.response?.url ?? file.thumbUrl;
- visible.value = true;
- };
- // 填写随访内容
- function subFollowContent() {
- activeObj.value.fillin.upImg = upImgList.value[0]?.response?.url;
- activeObj.value.fillin.downImg = downImageList.value[0]?.response?.url;
- activeObj.value.fillin.faceImg = faceImageList.value[0]?.response?.url;
- symptomsList.value = [];
- activeObj.value.symptomsData.forEach((item) => {
- symptomsList.value.push({ name: item.name, type: item.selectedValue });
- });
- activeObj.value.fillin.symptomsList = symptomsList.value;
- FillFollowContentMethod(activeObj.value).then(() => {
- notification.success({
- message: '',
- description: '提交成功!',
- });
- emits('submit');
- load();
- });
- }
- // 取消提交
- function cancelFollowContent() {
- VxeUI.modal.close(`follow-modal`);
- }
- function customUpload(e: any) {
- // uploadApi 你的二次封装上传接口
- UploadIFile(e.file)
- .then((res) => {
- // 调用实例的成功方法通知组件该文件上传成功
- e.onSuccess(res, e);
- })
- .catch((err) => {
- // 调用实例的失败方法通知组件该文件上传失败
- e.onError(err);
- });
- }
- const visible = ref<boolean>(false);
- const setVisible = (value: boolean): void => {
- visible.value = value;
- };
- const previewImg = ref<string>('');
- </script>
- <template>
- <div>
- <div class="flex font-bold">
- <!-- 左边-->
- <div class="animated-vertical-tabs">
- <div class="tab-list">
- <div class="font-bold h-8 pt-3 mb-3 ml-2">{{ activeObj?.followupPlanName }}</div>
- <div
- style="font-size: 14px"
- v-for="(content, index) in contentArr"
- :key="content.id"
- class="tab-item mb-3"
- :class="{ active: activeKey === content.id }"
- @click="changeTab(content, index)"
- >
- <div>{{ content.followupTaskName }}</div>
- <span class="tab-label">{{ content.arrangeTime }}</span>
- <div :class="content.progress == 1 ? 'text-red-600' : content.progress == 2 ? 'text-green-900' : content.progress == 3 ? 'text-blue-900' : ''">
- {{ content.progress === '1' ? '未完成' : content.progress === '2' ? '已完成' : '未开始' }}
- </div>
- </div>
- </div>
- </div>
- <!-- 右边-->
- <div :key="activeObj.id">
- <div class="h-8 text-center">
- {{ activeObj?.followupTaskName }}
- </div>
- <div class="mb-2 text-center">预定随访时间:{{ activeObj?.arrangeTime }}</div>
- <div class="mb-2 ml-2">
- 您好,您于<span class="text-blue-600">【{{ activeObj?.medicalTime }}】</span>在我院<span class="text-blue-600">【{{ activeObj?.institutionName }}】</span>因为<span
- class="text-blue-600"
- >【{{ activeObj?.diagnosis }}】</span
- >就诊。接下来我们将对您进行一个随访,请根据目前的实际情况回答。
- </div>
- <div
- class="border-1 border-solid border-gray:50 pl-2 pd-10 ml-2"
- v-if="(activeObj?.progress === '1' && activeObj?.symptomsData?.length > 0) || (activeObj.progress === '2' && activeObj?.fillin?.symptomsList?.length > 0)"
- >
- <div class="mb-3 border-b-0">
- 1、请问您的症状有没有<span class="text-red-600">好转</span>或者<span class="text-red-600">恶化</span>?请先点击症状,再选择好转还是恶化。(没有操作的症状默认没有变化)
- </div>
- <div class="ml-4" v-if="activeObj.progress === '1'">
- <!-- 症状选择器 -->
- <div class="symptom-container flex flex-wrap">
- <div v-for="item in activeObj?.symptomsData" :key="item.name" class="symptom-item">
- <div class="symptom-button" @click="!isReadOnly ? handleParentClick(item.name) : ''" :class="{ 'disabled': isReadOnly }">
- <span>{{ item.name }}</span>
- <span v-if="item.selectedValue" class="selected-value">: {{ item.selectedValue }}</span>
- </div>
- <div v-show="symptomsValue.parent === item.name" class="symptom-options">
- <a-radio-group :model-value="item.selectedId" @change="!isReadOnly && handleChildChange($event)" class="flex flex-wrap" :disabled="isReadOnly">
- <a-radio :value="tag.value" v-for="tag in item.child" :key="tag.value" class="mr-4">
- {{ tag.label }}
- </a-radio>
- </a-radio-group>
- </div>
- </div>
- </div>
- </div>
- <!-- 已经评估过 -->
- <div v-else>
- <div class="symptom-container flex flex-wrap">
- <div v-for="item in activeObj?.fillin?.symptomsList" :key="item.name" class="symptom-item">
- <div class="symptom-button">
- <span>{{ item.name }}</span>
- <span class="selected-value">: {{ item.type }}</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 第二个-->
- <div class="border-1 border-solid border-gray:50 pl-2 pd-10 ml-2">
- <div class="mb-3">2、请问有没有出现<span class="text-red-600">新</span>的症状?</div>
- <div class="mb-8 ml-4 flex">
- <div v-for="symptoms in selectSymptomsData" :key="symptoms.name" class="mr-4" @click="!isReadOnly ? changeTag(symptoms) : ''">
- <div>
- <div class="border-solid b-1 w-20 text-center" :class="[activeObj.fillin.isHaveNewSyndrome === symptoms.id ? 'bg-blue text-#fff' : '', activeObj.progress === '0' ? 'disabled' : '']">
- {{ symptoms.name }}
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 第三个-->
- <div class="border-1 border-solid border-gray:50 pl-2 pd-10 ml-2" v-if="activeObj.fillin?.isHaveNewSyndrome === 'Y'">
- <div class="mb-3">3、请描述新的症状</div>
- <div class="mb-4 ml-4">
- <a-input v-model:value="activeObj.fillin.newSyndrome" placeholder="请输入" :auto-size="{ minRows: 2, maxRows: 5 }" :disabled="isReadOnly" />
- </div>
- </div>
- <!-- 第四个-->
- <div class="border-1 border-solid border-gray:50 pl-2 pd-10 ml-2">
- <div class="mb-3">4、如果有其他情况,请留言</div>
- <div class="mb-4 ml-4">
- <a-input v-model:value="activeObj.fillin.otherDesc" placeholder="请输入" :auto-size="{ minRows: 2, maxRows: 5 }" :disabled="isReadOnly" />
- </div>
- </div>
- <!-- 第五个-->
- <div class="border-1 border-solid border-gray:50 pl-2 pd-10 ml-2">
- <div class="mb-3">5、为了医生更好地了解您的恢复情况,需要您上传舌面象照片</div>
- <div class="mb-4 ml-3">
- <!-- 上传图片-->
- <div class="flex">
- <!-- 舌面-->
- <div class="flex flex-col items-center mr-4">
- <a-upload
- :showUploadList="uploadProps"
- v-model:file-list="upImgList"
- list-type="picture-card"
- @preview="handlePreview"
- :maxCount="1"
- :customRequest="customUpload"
- :disabled="isReadOnly"
- >
- <div v-if="upImgList.length < 1">
- <plus-outlined />
- </div>
- </a-upload>
- <div class="font-bold">舌面</div>
- </div>
- <!-- 舌下-->
- <div class="flex flex-col items-center mr-4">
- <a-upload
- :showUploadList="uploadProps"
- v-model:file-list="downImageList"
- list-type="picture-card"
- @preview="handlePreview"
- :maxCount="1"
- :customRequest="customUpload"
- :disabled="isReadOnly"
- >
- <div v-if="downImageList.length < 1">
- <plus-outlined />
- </div>
- </a-upload>
- <div class="font-bold">舌下</div>
- </div>
- <!-- 面部-->
- <div class="flex flex-col items-center mr-4">
- <a-upload
- :showUploadList="uploadProps"
- v-model:file-list="faceImageList"
- list-type="picture-card"
- @preview="handlePreview"
- :maxCount="1"
- :customRequest="customUpload"
- :disabled="isReadOnly"
- >
- <div v-if="faceImageList.length < 1">
- <plus-outlined />
- </div>
- </a-upload>
- <div class="font-bold">面部</div>
- </div>
- <!-- --------end-->
- </div>
- <a-image
- :width="200"
- :style="{ display: 'none' }"
- :preview="{
- visible,
- onVisibleChange: setVisible,
- }"
- :src="previewImg"
- />
- </div>
- </div>
- <!-- -->
- <div class="ml-2 mt-1" v-if="contentArr.length > 1">
- 感谢您的配合,为了更好地了解您的回复情况,我们将会在<span class="text-blue-600"> {{ activeObj?.arrangeTime }} </span
- >再次对您进行随访,届时请点击随访链接参与,再次感谢您!
- </div>
- <div class="ml-2 mt-1 mb-6" v-else>
- 感谢您的耐心配合。
- </div>
- </div>
- </div>
- <div class="flex items-center justify-center mb-6" v-if="activeObj.progress === '1' && !isReadOnly">
- <a-button size="small" class="mr-4" @click="cancelFollowContent">取消</a-button>
- <a-button type="primary" size="small" @click="subFollowContent" :disabled="isReadOnly">提交</a-button>
- </div>
- </div>
- </template>
- <style scoped lang="scss">
- .ant-upload-select-picture-card i {
- font-size: 32px;
- color: #999;
- }
- .ant-upload-select-picture-card .ant-upload-text {
- margin-top: 8px;
- color: #666;
- }
- .mesh-grid {
- border-collapse: collapse;
- }
- .mesh-grid td {
- border: 1px solid black;
- width: 100px;
- padding: 20px 20px;
- text-align: center;
- }
- .animated-vertical-tabs {
- display: flex;
- // height: 730px;
- width: 17%;
- overflow: auto;
- }
- .tab-list {
- border-right: 1px solid #f0f0f0;
- }
- .tab-item {
- position: relative;
- padding: 10px;
- cursor: pointer;
- transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
- font-weight: bold;
- }
- .tab-item:hover {
- background-color: rgba(24, 144, 255, 0.06);
- }
- .tab-item.active {
- background: lightgray;
- }
- .tab-indicator {
- position: absolute;
- top: 0;
- right: -1px;
- width: 2px;
- height: 100%;
- background-color: #1890ff;
- transform: scaleY(0);
- transform-origin: center top;
- transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
- }
- .tab-item.active .tab-indicator {
- transform: scaleY(1);
- }
- .tab-content {
- flex: 1;
- padding: 0 24px;
- overflow: auto;
- }
- .fade-enter-active,
- .fade-leave-active {
- transition: opacity 0.3s ease;
- }
- .fade-enter-from,
- .fade-leave-to {
- opacity: 0;
- }
- // 症状选择器样式
- .symptom-container {
- .symptom-item {
- position: relative;
- margin-right: 16px;
- margin-bottom: 16px;
- .symptom-button {
- min-width: 100px;
- padding: 8px 16px;
- border: 1px solid #d9d9d9;
- border-radius: 4px;
- text-align: center;
- cursor: pointer;
- transition: all 0.3s;
- background: #fff;
- display: flex;
- align-items: center;
- justify-content: center;
- &:hover {
- border-color: #40a9ff;
- color: #40a9ff;
- }
- &.active {
- border-color: #1890ff;
- color: #1890ff;
- background: rgba(24, 144, 255, 0.1);
- }
- &.has-value {
- border-color: #52c41a;
- color: #52c41a;
- }
- .selected-value {
- margin-left: 4px;
- font-weight: 500;
- }
- }
- .symptom-options {
- position: absolute;
- top: 100%;
- left: 0;
- z-index: 1;
- margin-top: 8px;
- padding: 8px;
- background: #fff;
- border: 1px solid #d9d9d9;
- border-radius: 4px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
- animation: fadeIn 0.3s;
- .ant-radio-group {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .ant-radio-wrapper {
- padding: 4px 8px;
- border-radius: 4px;
- transition: all 0.3s;
- &:hover {
- background: rgba(24, 144, 255, 0.1);
- }
- }
- }
- }
- }
- // 已选择症状标签样式
- .selected-symptoms {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-bottom: 16px;
- .ant-tag {
- margin: 0;
- padding: 4px 8px;
- border-radius: 4px;
- transition: all 0.3s;
- &:hover {
- background: rgba(24, 144, 255, 0.1);
- }
- }
- }
- // 动画
- @keyframes fadeIn {
- from {
- opacity: 0;
- transform: translateY(-10px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
- }
- // 整体布局优化
- .border-1 {
- margin-bottom: 16px;
- padding: 16px;
- border-radius: 8px;
- background: #fff;
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
- }
- .mb-8 {
- margin-bottom: 32px;
- }
- .ml-4 {
- margin-left: 16px;
- }
- .text-blue-600 {
- color: #1890ff;
- }
- .text-red-600 {
- color: #ff4d4f;
- }
- .font-bold {
- font-weight: 600;
- }
- // 禁用状态样式
- .disabled {
- opacity: 0.5;
- cursor: not-allowed !important;
- pointer-events: none;
- }
- .symptom-button.disabled {
- background-color: #f5f5f5;
- color: #999;
- border-color: #d9d9d9;
- cursor: not-allowed;
- }
- .symptom-button.disabled:hover {
- border-color: #d9d9d9;
- color: #999;
- background-color: #f5f5f5;
- }
- </style>
|