index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. <template>
  2. <div class="app-container">
  3. <div class="left-menu">
  4. <div class="back-button" @click="goBack">
  5. <el-icon>
  6. <ArrowLeft />
  7. </el-icon>
  8. 返回患者列表
  9. </div>
  10. <el-menu :default-active="activeMenu" class="medical-menu" @select="handleSelect">
  11. <el-menu-item index="medicalRecord">
  12. <span>营养病例</span>
  13. </el-menu-item>
  14. <el-menu-item index="visitRecord">
  15. <span>就诊记录</span>
  16. </el-menu-item>
  17. <el-menu-item index="checkLabel">
  18. <span>检查指标</span>
  19. </el-menu-item>
  20. <el-menu-item index="nutritionScreeningAdd" v-show="false">
  21. <span>营养筛查新增</span>
  22. </el-menu-item>
  23. <el-menu-item index="nutritionScreening">
  24. <span>营养筛查</span>
  25. </el-menu-item>
  26. <el-menu-item index="nutritionEvaluationAdd" v-show="false">
  27. <span>营养评估新增</span>
  28. </el-menu-item>
  29. <el-menu-item index="nutritionEvaluation">
  30. <span>营养评估</span>
  31. </el-menu-item>
  32. <el-menu-item index="nutritionDiagnosis">
  33. <span>营养诊断</span>
  34. </el-menu-item>
  35. <el-sub-menu index="nutritionIntervention">
  36. <template #title>营养干预</template>
  37. <el-menu-item index="nutritionSetting">营养设定</el-menu-item>
  38. <el-menu-item index="enteralNutrition">肠内营养</el-menu-item>
  39. <el-menu-item index="parenteralNutrition">肠外营养</el-menu-item>
  40. <el-menu-item index="dietTherapy">膳食治疗</el-menu-item>
  41. </el-sub-menu>
  42. <el-menu-item index="nutritionMonitor">
  43. <span>营养监测</span>
  44. </el-menu-item>
  45. <el-menu-item index="nutritionEducation">
  46. <span>营养宣教</span>
  47. </el-menu-item>
  48. </el-menu>
  49. <div class="search-bar">
  50. <el-input v-model="searchValue" placeholder="姓名/床号/门诊号" size="small" class="search-input" @keyup.enter="handleSearch" />
  51. <el-button size="small" type="primary" class="search-btn" @click="handleSearch">搜索</el-button>
  52. </div>
  53. <div class="patient-tabs">
  54. <el-tabs v-model="patientTab" size="small" stretch @tab-change="handleTabChange">
  55. <el-tab-pane :label="`待诊${waitingCount}`" name="wait" />
  56. <el-tab-pane :label="`诊中${treatingCount}`" name="doing" />
  57. <el-tab-pane :label="`已诊${treatedCount}`" name="done" />
  58. </el-tabs>
  59. </div>
  60. <div class="patient-table">
  61. <el-table
  62. :data="patientList"
  63. border
  64. size="small"
  65. height="80%"
  66. :show-header="true"
  67. class="table-patients"
  68. :row-class-name="tableRowClassName"
  69. @row-click="handleRowClick"
  70. >
  71. <el-table-column prop="name" label="姓名" width="60" />
  72. <el-table-column prop="gender" label="性别" width="50" />
  73. <el-table-column prop="age" label="年龄" width="80" />
  74. </el-table>
  75. </div>
  76. <div class="nav-btns">
  77. <el-button size="small" @click="handlePrev">上一位</el-button>
  78. <el-button size="small" type="primary" @click="handleNext">下一位</el-button>
  79. </div>
  80. </div>
  81. <div class="main-content">
  82. <el-row>
  83. <div class="patient-left-info">
  84. <el-row class="info-row">
  85. <span class="name">{{ patientInfo.name }}</span>
  86. <span class="age">{{ patientInfo.age }}</span>
  87. <span class="gender-icon" :class="patientInfo.gender === '男' ? 'male' : 'female'">
  88. {{ patientInfo.gender === '男' ? '♂' : '♀' }}
  89. </span>
  90. </el-row>
  91. <el-row style="margin-top: 10px; margin-left: 10%">
  92. <el-button size="small" @click="handleEdit">编辑</el-button>
  93. <el-button size="small" type="primary" @click="handleDetail">详情</el-button>
  94. </el-row>
  95. </div>
  96. <div class="patient-right-info">
  97. <el-row>
  98. <span>门诊号:{{ patientInfo.outpatientNo }}</span>
  99. <span style="margin-left: 30px">科室:{{ patientInfo.deptName }}</span>
  100. <span style="margin-left: 30px">营养诊断:</span>
  101. </el-row>
  102. <el-row>
  103. <span>会诊结果:</span>
  104. </el-row>
  105. </div>
  106. </el-row>
  107. <component :is="currentComponent" v-if="currentComponent" :patient-info="patientInfo" @change="handleSelect" />
  108. </div>
  109. <!-- 患者详情弹窗 -->
  110. <DetailDialog
  111. v-model:visible="showDetailDialog"
  112. :detail-data="detailData"
  113. :physical-activity-dict="physicalActivityDict"
  114. @close="handleDetailClose"
  115. />
  116. <!-- 新增:编辑弹窗 -->
  117. <EditDialog v-model:visible="showEditDialog" :edit-data="editData" @close="handleEditClose" @save="handleEditSave" />
  118. </div>
  119. </template>
  120. <script setup lang="ts">
  121. import {ref, onMounted, getCurrentInstance, toRefs, defineAsyncComponent} from 'vue';
  122. import {useRoute, useRouter} from 'vue-router';
  123. import {ArrowLeft} from '@element-plus/icons-vue';
  124. import {listTreatmentUser, getTreatmentUser} from '@/api/workbench/treatmentUser';
  125. import DetailDialog from './detailDialog.vue';
  126. import EditDialog from './editDialog.vue';
  127. // 动态导入组件
  128. const MedicalRecord = defineAsyncComponent(() => import('./levelMenu.vue'));
  129. const NutritionScreening = defineAsyncComponent(() => import('@/views/patients/screening/index.vue'));
  130. const NutritionEvaluation = defineAsyncComponent(() => import('@/views/patients/evaluation/index.vue'));
  131. const NutriDiagnosis = defineAsyncComponent(() => import('@/views/patients/nutriDiagnosis/index.vue'));
  132. const CheckLabel = defineAsyncComponent(() => import('@/views/patients/checkLabel/index.vue'));
  133. const NutritionScreeningAdd = defineAsyncComponent(() => import('@/views/patients/screening/add.vue'));
  134. const NutritionEvaluationAdd = defineAsyncComponent(() => import('@/views/patients/evaluation/add.vue'));
  135. const NutritionEducation = defineAsyncComponent(() => import('@/views/patients/nutritionEducation/index.vue'));
  136. const NutritionSetting = defineAsyncComponent(() => import('@/views/patients/nutritionSetting/index.vue'));
  137. const DietTherapy = defineAsyncComponent(() => import('@/views/patients/dietTherapy/index.vue'));
  138. const componentMap = {
  139. medicalRecord: MedicalRecord, // “营养病例”对应MedicalRecord.vue
  140. checkLabel: CheckLabel, // “检查标签”对应CheckLabel.vue
  141. nutritionDiagnosis: NutriDiagnosis,
  142. nutritionScreening: NutritionScreening,
  143. nutritionScreeningAdd: NutritionScreeningAdd,
  144. nutritionEvaluation: NutritionEvaluation,
  145. nutritionEvaluationAdd: NutritionEvaluationAdd,
  146. nutritionEducation: NutritionEducation,
  147. nutritionSetting: NutritionSetting,
  148. dietTherapy: DietTherapy
  149. // ... 其它映射
  150. };
  151. const currentComponent = ref(componentMap['medicalRecord']); // 默认显示
  152. const route = useRoute();
  153. const router = useRouter();
  154. const {proxy} = getCurrentInstance() as ComponentInternalInstance;
  155. const activeMenu = ref('medicalRecord');
  156. const patientInfo = ref({
  157. id: '',
  158. name: '',
  159. age: '',
  160. gender: '',
  161. type: '',
  162. deptId: '',
  163. deptName: '',
  164. outpatientNo: ''
  165. });
  166. // 弹窗控制
  167. const showDetailDialog = ref(false);
  168. const detailData = ref({});
  169. const physicalActivityDict = ref([]);
  170. const showEditDialog = ref(false);
  171. const editData = ref({} as any);
  172. const handleSelect = (key: string, other?: string[]) => {
  173. if (key == 'nutritionScreeningAdd' || key == 'nutritionEvaluationAdd') {
  174. activeMenu.value = key.replace('Add', '');
  175. } else {
  176. activeMenu.value = key;
  177. }
  178. currentComponent.value = componentMap[key] || null;
  179. patientInfo.value['other'] = other;
  180. patientInfo.value.type = route.query.type;
  181. patientInfo.value.outpatientNo = route.query.outpatientNo;
  182. };
  183. const goBack = () => {
  184. router.back();
  185. };
  186. const searchValue = ref('');
  187. const patientTab = ref('wait');
  188. const patientList = ref([]);
  189. const loading = ref(false);
  190. const currentPatientIndex = ref(0);
  191. const tableRowClassName = ({row, rowIndex}) => {
  192. return rowIndex === currentPatientIndex.value ? 'current-row' : '';
  193. };
  194. const handleRowClick = (row, column, event) => {
  195. activeMenu.value = 'medicalRecord';
  196. currentComponent.value = componentMap['medicalRecord'];
  197. const idx = patientList.value.findIndex((item) => item === row);
  198. patientInfo.value = row;
  199. patientInfo.value.type = route.query.type;
  200. patientInfo.value.outpatientNo = route.query.outpatientNo;
  201. if (idx !== -1) {
  202. currentPatientIndex.value = idx;
  203. }
  204. };
  205. const handlePrev = () => {
  206. activeMenu.value = 'medicalRecord';
  207. currentComponent.value = componentMap['medicalRecord'];
  208. if (currentPatientIndex.value > 0) {
  209. patientInfo.value = patientList.value[currentPatientIndex.value - 1];
  210. patientInfo.value.type = route.query.type;
  211. patientInfo.value.outpatientNo = route.query.outpatientNo;
  212. currentPatientIndex.value--;
  213. }
  214. };
  215. const handleNext = () => {
  216. activeMenu.value = 'medicalRecord';
  217. currentComponent.value = componentMap['medicalRecord'];
  218. patientInfo.value = patientList.value[currentPatientIndex.value + 1];
  219. patientInfo.value.type = route.query.type;
  220. patientInfo.value.outpatientNo = route.query.outpatientNo;
  221. if (currentPatientIndex.value < patientList.value.length - 1) {
  222. currentPatientIndex.value++;
  223. }
  224. };
  225. const handleTabChange = (tab: string) => {
  226. if (tab == 'wait') {
  227. getList();
  228. } else {
  229. patientList.value = [];
  230. }
  231. };
  232. // 编辑按钮处理
  233. const handleEdit = async () => {
  234. if (!patientInfo.value.id) {
  235. proxy?.$modal.msgError('请先选择患者');
  236. return;
  237. }
  238. try {
  239. // 获取患者详细信息
  240. const res = await getTreatmentUser(patientInfo.value.id);
  241. editData.value = res.data;
  242. showEditDialog.value = true;
  243. } catch (error) {
  244. console.error('获取患者详情失败:', error);
  245. proxy?.$modal.msgError('获取患者详情失败');
  246. }
  247. };
  248. // 详情按钮处理
  249. const handleDetail = async () => {
  250. if (!patientInfo.value.id) {
  251. proxy?.$modal.msgError('请先选择患者');
  252. return;
  253. }
  254. try {
  255. // 获取患者详细信息
  256. const res = await getTreatmentUser(patientInfo.value.id);
  257. detailData.value = res.data;
  258. showDetailDialog.value = true;
  259. } catch (error) {
  260. console.error('获取患者详情失败:', error);
  261. proxy?.$modal.msgError('获取患者详情失败');
  262. }
  263. };
  264. // 详情弹窗关闭处理
  265. const handleDetailClose = () => {
  266. showDetailDialog.value = false;
  267. detailData.value = {};
  268. };
  269. const handleEditClose = () => {
  270. showEditDialog.value = false;
  271. editData.value = {};
  272. };
  273. const handleEditSave = (data) => {
  274. // 可在此处调用保存API,保存后刷新列表
  275. showEditDialog.value = false;
  276. proxy?.$modal.msgSuccess('保存成功');
  277. getList();
  278. };
  279. const waitingCount = ref(0);
  280. const treatingCount = ref(0);
  281. const treatedCount = ref(0);
  282. const getList = async () => {
  283. loading.value = true;
  284. try {
  285. const params: any = {
  286. pageNum: 1,
  287. pageSize: 10
  288. };
  289. if (searchValue.value) {
  290. params.searchFlag = searchValue.value;
  291. }
  292. const res = await listTreatmentUser(params);
  293. console.log('res', JSON.stringify(res));
  294. patientList.value = (res.rows || []).map((item) => ({
  295. id: item.id,
  296. name: item.treatName,
  297. type: item.type, // 0: 门诊,1: 住院
  298. deptId: item.doorId,
  299. deptName: item.deptName,
  300. outpatientNo: item.outpatientNo,
  301. gender: item.sex === '0' ? '男' : item.sex === '1' ? '女' : '',
  302. age: item.age
  303. }));
  304. waitingCount.value = patientList.value.length;
  305. // 如果有患者数据,根据路由参数或默认选中第一个
  306. if (patientList.value.length > 0) {
  307. const {id} = route.query;
  308. if (id) {
  309. // 查找从工作台点击进入的患者
  310. const targetIndex = patientList.value.findIndex((patient) => patient.id === id);
  311. if (targetIndex !== -1) {
  312. patientInfo.value = patientList.value[targetIndex];
  313. patientInfo.value.type = route.query.type;
  314. patientInfo.value.outpatientNo = route.query.outpatientNo;
  315. currentPatientIndex.value = targetIndex;
  316. } else {
  317. // 如果没找到,默认选中第一个
  318. patientInfo.value = patientList.value[0];
  319. currentPatientIndex.value = 0;
  320. }
  321. } else {
  322. // 没有路由参数,默认选中第一个
  323. patientInfo.value = patientList.value[0];
  324. patientInfo.value.type = route.query.type;
  325. patientInfo.value.outpatientNo = route.query.outpatientNo;
  326. currentPatientIndex.value = 0;
  327. }
  328. }
  329. loading.value = false;
  330. } catch (error) {
  331. console.error('获取列表失败:', error);
  332. loading.value = false;
  333. }
  334. };
  335. const handleSearch = () => {
  336. getList();
  337. };
  338. onMounted(() => {
  339. // 获取体力活动字典
  340. const {physical_activity} = toRefs(proxy?.useDict('physical_activity'));
  341. physicalActivityDict.value = physical_activity?.value || [];
  342. patientInfo.value.type = route.query.type;
  343. patientInfo.value.outpatientNo = route.query.outpatientNo;
  344. getList();
  345. });
  346. </script>
  347. <style lang="scss" scoped>
  348. .app-container {
  349. display: flex;
  350. height: calc(100vh - 50px);
  351. background-color: #f5f7fa;
  352. margin: -10px;
  353. }
  354. .left-menu {
  355. width: 220px;
  356. background-color: #fff;
  357. border-right: 1px solid #e6e6e6;
  358. display: flex;
  359. flex-direction: column;
  360. padding-bottom: 60px;
  361. /* 增加底部padding,为按钮留出空间 */
  362. position: relative;
  363. overflow: hidden;
  364. /* 防止内容溢出 */
  365. }
  366. .back-button {
  367. padding: 16px;
  368. display: flex;
  369. align-items: center;
  370. gap: 8px;
  371. cursor: pointer;
  372. color: #409eff;
  373. border-bottom: 1px solid #e6e6e6;
  374. font-size: 14px;
  375. .el-icon {
  376. font-size: 16px;
  377. }
  378. &:hover {
  379. background-color: #f5f7fa;
  380. }
  381. }
  382. .medical-menu {
  383. border-right: none;
  384. :deep(.el-menu-item) {
  385. font-size: 14px;
  386. height: 40px;
  387. line-height: 40px;
  388. }
  389. }
  390. .search-bar {
  391. display: flex;
  392. align-items: center;
  393. padding: 8px 12px 0 12px;
  394. gap: 6px;
  395. }
  396. .search-input {
  397. flex: 1;
  398. }
  399. .search-btn {
  400. margin-left: 0;
  401. }
  402. .patient-tabs {
  403. padding: 0 12px;
  404. margin-top: 8px;
  405. }
  406. .patient-table {
  407. min-width: 220px;
  408. overflow-x: auto;
  409. }
  410. .table-patients {
  411. table-layout: fixed;
  412. width: 100%;
  413. min-width: 220px;
  414. white-space: nowrap;
  415. .el-table__header th {
  416. background: #f5f7fa;
  417. color: #409eff;
  418. font-weight: bold;
  419. font-size: 13px;
  420. padding: 4px 0;
  421. }
  422. .el-table__row {
  423. cursor: pointer;
  424. }
  425. .el-table__row.current-row {
  426. background: #eaf4ff !important;
  427. }
  428. }
  429. .nav-btns {
  430. display: flex;
  431. justify-content: space-between;
  432. gap: 8px;
  433. position: absolute;
  434. left: 0;
  435. right: 0;
  436. bottom: 12px;
  437. padding: 0 12px;
  438. background: #fff;
  439. z-index: 1;
  440. width: calc(100% - 24px);
  441. margin: 0 auto;
  442. }
  443. .main-content {
  444. flex: 1;
  445. padding: 20px;
  446. overflow-y: auto;
  447. }
  448. .patient-left-info {
  449. background-color: #fff;
  450. height: 100px;
  451. padding: 10px;
  452. border-radius: 4px;
  453. margin-bottom: 20px;
  454. width: 11%;
  455. }
  456. .patient-right-info {
  457. background-color: #fff;
  458. height: 100px;
  459. padding: 20px;
  460. border-radius: 4px;
  461. margin-bottom: 20px;
  462. width: 86%;
  463. margin-left: 30px;
  464. }
  465. .info-row {
  466. display: flex;
  467. align-items: center;
  468. white-space: nowrap;
  469. }
  470. .info-row .name {
  471. font-size: 22px;
  472. }
  473. .info-row .age {
  474. margin-left: 30px;
  475. }
  476. .gender-icon {
  477. margin-left: 30px;
  478. font-size: 25px;
  479. font-weight: bold;
  480. line-height: 1;
  481. display: inline-block;
  482. vertical-align: middle;
  483. }
  484. .gender-icon.female {
  485. color: #ff4949;
  486. }
  487. .gender-icon.male {
  488. color: #409eff;
  489. }
  490. </style>