Detail.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. <template>
  2. <el-form ref="formRef" :model="dataForm" label-width="100px" :toggleType="2">
  3. <div class="elder-detail-wrap">
  4. <div class="left" :style="infoStyle">
  5. <ContentWrap class="elderInfo">
  6. <div class="header">
  7. <h4 class="title">长者信息</h4>
  8. </div>
  9. <div style="margin-top: 10px">
  10. <el-button @click="showCardBad" type="warning" size="small">床头卡</el-button>
  11. <el-button @click="showCardDoor" type="danger" size="small">门头卡</el-button>
  12. <el-button @click="showCardShare" type="success" size="small">分享卡</el-button>
  13. </div>
  14. <div class="info-wrap">
  15. <el-image :src="dataForm.elderCover" style="width: 80px; height: 80px">
  16. <template #placeholder>
  17. <div>
  18. <img src="@/assets/imgs/old_man.png" style="width: 80px; height: 80px" alt="" />
  19. </div>
  20. </template>
  21. <template #error>
  22. <div>
  23. <img src="@/assets/imgs/old_man.png" style="width: 80px; height: 80px" alt="" />
  24. </div>
  25. </template>
  26. </el-image>
  27. <div class="info">
  28. <div class="top">
  29. <span class="f600">{{ dataForm.elderName }}</span>
  30. <span class="grey">{{
  31. getDictLabel(DICT_TYPE.SYSTEM_USER_SEX, dataForm.elderSex)
  32. }}</span>
  33. <span class="grey">{{ dataForm.elderAge }}</span>
  34. </div>
  35. <div>
  36. <el-tag type="warning">中度失能</el-tag>
  37. <el-tag type="primary" class="ml3">{{ dataForm.nurseLevelName }}</el-tag>
  38. </div>
  39. </div>
  40. </div>
  41. <div class="detail">
  42. <div class="bed">
  43. <span class="room">床号/房号:</span>
  44. <span>{{ dataForm.bedName }}</span>
  45. </div>
  46. <div class="box">
  47. <div class="colomn">
  48. <span class="f500">{{
  49. dataForm.checkInTime ? formatTime(dataForm.checkInTime, 'yyyy-MM-dd') : ''
  50. }}</span>
  51. <span class="f12">入住日期</span>
  52. </div>
  53. <div class="colomn">
  54. <span class="f500">{{ dataForm.stayDay }}天</span>
  55. <span class="f12">在院天数</span>
  56. </div>
  57. <div class="colomn">
  58. <span class="f500">{{ dataForm.stayNumber }}次</span>
  59. <span class="f12">入住次数</span>
  60. </div>
  61. </div>
  62. <el-progress :percentage="dataForm.infoPercentage">
  63. <template> </template>
  64. </el-progress>
  65. <div class="desc">
  66. <span class="grey">信息完善程度</span>
  67. <div>
  68. {{ dataForm.infoPercentage }}%
  69. <!-- <span class="num">{{ dataForm.infoFieldCount }}</span>/{{ dataForm.totalInfoFieldCount }} -->
  70. </div>
  71. </div>
  72. <div class="contract"> 档案号:{{ dataForm.fileNumber }} </div>
  73. </div>
  74. <div class="scene">
  75. <div class="title">业务场景</div>
  76. <div v-for="(item, index) in arr" :key="item.name" class="scene-item">
  77. <span class="line" v-if="index == 0 || index == arr.length - 1"></span>
  78. <div class="timeline-item__tail" v-if="index > 0"></div>
  79. {{ item.name }}
  80. </div>
  81. </div>
  82. <div class="btns">
  83. <el-button
  84. class="btn"
  85. type="primary"
  86. v-for="(item, index) in routeArr"
  87. :key="index"
  88. :disabled="item.show"
  89. plain
  90. @click="handleTo(item)"
  91. >{{ item.label }}</el-button
  92. >
  93. </div>
  94. </ContentWrap>
  95. </div>
  96. <div class="right" :style="contentStyle">
  97. <ContentWrap>
  98. <el-tabs class="demo-tabs" v-model="activeName" @tab-change="handleTabChange">
  99. <ElScrollbar :height="!smallerThanLg ? '78.5vh' : ''" class="tab-content">
  100. <el-tab-pane
  101. v-for="(item, index) in tabs"
  102. :key="index"
  103. :label="item.label"
  104. :name="item.value"
  105. style="padding-top: 14px"
  106. >
  107. <component
  108. :is="item.comp"
  109. :dataForm="dataForm"
  110. :ref="(el) => setFormRef(el, index)"
  111. />
  112. </el-tab-pane>
  113. </ElScrollbar>
  114. </el-tabs>
  115. </ContentWrap>
  116. </div>
  117. </div>
  118. </el-form>
  119. <!-- 使用通用床头卡弹窗组件 -->
  120. <BedCardModal
  121. ref="bedCardModalRef"
  122. v-model="dialogVisible"
  123. :patientData="dataForm"
  124. @import-card="importCardFun"
  125. :title="'床头卡预览'"
  126. :width="'1080px'"
  127. @edit="handleEdit"
  128. @close="handleDialogClose"
  129. />
  130. <!-- 分享卡弹窗组件 -->
  131. <ShareCardModal
  132. ref="shareCardModalRef"
  133. v-model="dialogVisibleShare"
  134. :patientData="dataForm"
  135. @import-card="importCardFun"
  136. :title="'分享卡预览'"
  137. :width="'900px'"
  138. @close="handleDialogClose"
  139. />
  140. <!-- 门头卡弹窗组件 -->
  141. <DoorCardModal
  142. ref="doorCardModalRef"
  143. v-model="dialogVisibleDoor"
  144. :patientData="dataForm"
  145. @import-card="importCardDoorFun"
  146. :title="'门头卡预览'"
  147. :width="'600px'"
  148. @close="handleDialogClose"
  149. />
  150. <Form ref="formRefE" @success="getElderInfoById(idE)" />
  151. </template>
  152. <script setup lang="ts">
  153. import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
  154. import { ElScrollbar } from 'element-plus'
  155. import BedCardModal from '@/components/BedCardModal/index.vue'
  156. import ShareCardModal from '@/components/ShareCardModal/index.vue'
  157. import DoorCardModal from '@/components/DoorCardModal/index.vue'
  158. import { getElderInfoById } from '@/api/elderly/elder/elderly-Info'
  159. import { useAppStore } from '@/store/modules/app'
  160. import { usePermissionStore } from '@/store/modules/permission'
  161. import { findTreeNode } from '@/utils/tree'
  162. import { getPatientData, importCard, getDoorCardPatientData } from '@/utils/outBedCard'
  163. import First from './First.vue'
  164. import Second from './Second.vue'
  165. import Third from './Third.vue'
  166. import Four from './Four.vue'
  167. import Five from './Five.vue'
  168. import Six from './Six.vue'
  169. import Seven from './Seven.vue'
  170. import Eight from './Eight.vue'
  171. import Nine from './Nine.vue'
  172. import adaptationTab from './adaptationTab.vue'
  173. import floorActivityTab from './floorActivityTab.vue'
  174. import carePlanTab from './carePlanTab.vue'
  175. import healthRecordTab from './healthRecordTab.vue'
  176. import { formatTime } from '@/utils'
  177. import { getElderHealthRecordById } from '@/api/elderly/elder/health-record'
  178. import { getDictLabel, DICT_TYPE } from '@/utils/dict'
  179. import Form from '@/views/elderly/elder/health-record/Form.vue'
  180. defineOptions({ name: 'ElderFileDetail' })
  181. const formRef = ref() // 表单 Ref
  182. const formRefE = ref() // 表单 Ref
  183. const bed = ref() // 表单 Ref
  184. const dataForm = ref<{
  185. elderName?: string
  186. elderSex?: string
  187. elderAge?: string
  188. nurseLevelName?: string
  189. bedName?: string
  190. checkInTime?: Date
  191. stayDay?: string
  192. stayNumber?: string
  193. infoPercentage?: number
  194. fileNumber?: string
  195. }>({})
  196. const router = useRouter()
  197. const permissionStore = usePermissionStore()
  198. const appStore = useAppStore()
  199. const layout = computed(() => appStore.getLayout)
  200. const tabs = [
  201. { label: '长者信息', value: 1, comp: First },
  202. { label: '家属信息', value: 2, comp: Second },
  203. { label: '合同信息', value: 3, comp: Third },
  204. { label: '费用信息', value: 4, comp: Four },
  205. { label: '能力评估', value: 5, comp: Five },
  206. { label: '护理计划', value: 6, comp: Six },
  207. { label: '活动相册', value: 7, comp: Seven },
  208. { label: '附件', value: 8, comp: Eight },
  209. { label: '操作记录', value: 9, comp: Nine },
  210. { label: '健康档案', value: 10, comp: healthRecordTab },
  211. { label: '入住适应服务', value: 11, comp: adaptationTab },
  212. { label: '楼层活动记录', value: 12, comp: floorActivityTab },
  213. { label: '长者照顾计划', value: 13, comp: carePlanTab }
  214. ]
  215. const arr = [
  216. { name: '入住' },
  217. { name: '入住申请' },
  218. { name: '能力评估' },
  219. { name: '入住审批通过' },
  220. { name: '入住办理' },
  221. { name: '在院' }
  222. ]
  223. const routers = computed(() =>
  224. unref(layout) === 'cutMenu' ? permissionStore.getMenuTabRouters : permissionStore.getRouters
  225. )
  226. const routeArr = [
  227. {
  228. label: '床位变更',
  229. value: '/elderly/bed-change',
  230. show: findTreeNode(routers.value, 'bed-change', 'path') ? false : true
  231. },
  232. {
  233. label: '餐饮变更',
  234. value: '/elderly/changes/ChangeFood/index',
  235. show: findTreeNode(routers.value, 'changes/ChangeFood/index', 'path') ? false : true
  236. },
  237. {
  238. label: '护理变更',
  239. value: '/elderly/nurse-change',
  240. show: findTreeNode(routers.value, 'nurse-change', 'path') ? false : true
  241. },
  242. {
  243. label: '价格变更',
  244. value: '/elderly/price-change',
  245. show: findTreeNode(routers.value, 'price-change', 'path') ? false : true
  246. },
  247. {
  248. label: '日常费用',
  249. value: '/fee/daily-fee',
  250. show: findTreeNode(routers.value, 'daily-fee', 'path') ? false : true
  251. },
  252. {
  253. label: '能力评估',
  254. value: '/apply/synthetic-ability',
  255. show: findTreeNode(routers.value, 'synthetic-ability', 'path') ? false : true
  256. },
  257. {
  258. label: '长者押金',
  259. value: '/fee/deposit',
  260. show: findTreeNode(routers.value, 'deposit', 'path') ? false : true
  261. },
  262. {
  263. label: '缴费管理',
  264. value: '/fee/bill-pay',
  265. show: findTreeNode(routers.value, 'bill-pay', 'path') ? false : true
  266. },
  267. {
  268. label: '生活护理计划',
  269. value: '/nursing/life-care-plan',
  270. show: findTreeNode(routers.value, 'life-care-plan', 'path') ? false : true
  271. },
  272. {
  273. label: '医疗护理计划',
  274. value: '/nursing/medical-care-plan',
  275. show: findTreeNode(routers.value, 'medical-care-plan', 'path') ? false : true
  276. }
  277. ]
  278. const route = useRoute()
  279. const healthRecord = ref({})
  280. let idE = undefined
  281. onMounted(async () => {
  282. const { id } = route.params
  283. idE = id
  284. activeName.value = 1
  285. // 获取长者详情
  286. try {
  287. const res = await getElderInfoById(id)
  288. console.log('长者详情:', res)
  289. dataForm.value = res
  290. } catch (e) {}
  291. try {
  292. const res = await getElderHealthRecordById(id)
  293. console.log('长者健康档案:', res)
  294. healthRecord.value = res
  295. } catch (e) {}
  296. })
  297. const activeName = ref(1)
  298. const handleTabChange = (tab) => {
  299. activeName.value = tab
  300. if (tab != 1 || tab != 2) {
  301. fcFormRefs.value[tab - 1].init()
  302. }
  303. }
  304. // 床头卡弹窗控制
  305. const dialogVisible = ref(false)
  306. const dialogVisibleShare = ref(false)
  307. const dialogVisibleDoor = ref(false)
  308. const bedCardModalRef = ref()
  309. const shareCardModalRef = ref()
  310. const doorCardModalRef = ref()
  311. // 显示床头卡弹窗
  312. const showCardBad = () => {
  313. dialogVisible.value = true
  314. nextTick(() => {
  315. bedCardModalRef.value.updatePatientData(getPatientData(healthRecord.value, dataForm.value))
  316. })
  317. }
  318. // 显示分享卡弹窗
  319. const showCardShare = () => {
  320. dialogVisibleShare.value = true
  321. nextTick(() => {
  322. shareCardModalRef.value.updatePatientData(getPatientData(healthRecord.value, dataForm.value))
  323. })
  324. }
  325. // 显示门头卡弹窗
  326. const showCardDoor = () => {
  327. dialogVisibleDoor.value = true
  328. nextTick(() => {
  329. doorCardModalRef.value.updatePatientData(getDoorCardPatientData(dataForm.value))
  330. })
  331. }
  332. // 处理弹窗关闭事件
  333. const handleDialogClose = () => {
  334. console.log('床头卡弹窗已关闭')
  335. }
  336. const handleEdit = () => {
  337. // 先显式关闭床头卡弹窗,再等关闭动画结束(约 300ms)后再跳转。
  338. // 否则 el-dialog 的关闭动画会被 <keep-alive> 的路由切换打断,
  339. // 弹窗内部动画状态会卡住,导致返回后再点关闭预览关不掉(log 仍会打)。
  340. dialogVisible.value = false
  341. setTimeout(() => {
  342. router.push({
  343. path: '/trend-charts/health-record'
  344. })
  345. }, 350)
  346. }
  347. const importCardFun = () => {
  348. importCard(healthRecord.value, dataForm.value)
  349. }
  350. const importCardDoorFun = () => {
  351. // cardType = 3 表示门头卡
  352. importCard(undefined, dataForm.value, 1, 3)
  353. }
  354. const fcFormRefs = ref<HTMLElement[]>([])
  355. const setFormRef = (el, index) => {
  356. if (el) {
  357. fcFormRefs.value[index] = el
  358. }
  359. }
  360. const handleTo = (item) => {
  361. router.push({
  362. path: item.value,
  363. query: { elderName: dataForm.value.elderName }
  364. })
  365. }
  366. const breakpoints = useBreakpoints(breakpointsTailwind)
  367. const smallerThanLg = breakpoints.smaller('lg')
  368. const infoStyle = computed(() => {
  369. if (!smallerThanLg.value) {
  370. return { width: '300px' }
  371. } else {
  372. return { width: '100%' }
  373. }
  374. })
  375. const contentStyle = computed(() => {
  376. if (!smallerThanLg.value) {
  377. return { width: 'calc(100% - 320px)', marginLeft: '20px' }
  378. } else {
  379. return { width: '100%' }
  380. }
  381. })
  382. </script>
  383. <style lang="scss" scoped>
  384. .elder-detail-wrap {
  385. display: flex;
  386. .left {
  387. display: inline-block;
  388. .elderInfo {
  389. .header {
  390. display: flex;
  391. justify-content: space-between;
  392. align-items: baseline;
  393. }
  394. .title {
  395. font-weight: 600;
  396. font-size: 16px;
  397. }
  398. .info-wrap {
  399. display: flex;
  400. img {
  401. width: 70px;
  402. height: 67px;
  403. }
  404. .info {
  405. flex: 3;
  406. display: flex;
  407. flex-direction: column;
  408. justify-content: space-between;
  409. padding: 0 10px;
  410. .top {
  411. display: flex;
  412. justify-content: space-between;
  413. align-items: center;
  414. .f600 {
  415. font-weight: 600;
  416. font-size: 18px;
  417. }
  418. .grey {
  419. font-weight: 400;
  420. font-size: 14px;
  421. color: #636363;
  422. }
  423. }
  424. }
  425. }
  426. .detail {
  427. .bed {
  428. padding: 10px;
  429. background-color: var(--el-color-primary-light-3);
  430. color: #fff;
  431. border-radius: 4px;
  432. font-size: 17px;
  433. .room {
  434. display: block;
  435. font-size: 12px;
  436. margin-bottom: 4px;
  437. }
  438. }
  439. .box {
  440. margin: 16px 0 22px 0;
  441. display: flex;
  442. justify-content: space-between;
  443. .colomn {
  444. display: flex;
  445. flex-direction: column;
  446. text-align: center;
  447. .f500 {
  448. font-size: 14px;
  449. font-weight: 500;
  450. margin-bottom: 6px;
  451. }
  452. .f12 {
  453. font-weight: 400;
  454. font-size: 12px;
  455. color: #7c7c7c;
  456. }
  457. }
  458. }
  459. .el-progress--line {
  460. display: block;
  461. }
  462. .desc {
  463. display: flex;
  464. justify-content: space-between;
  465. margin-top: 10px;
  466. font-size: 12px;
  467. .grey {
  468. font-weight: 400;
  469. color: #7c7c7c;
  470. }
  471. .num {
  472. color: var(--el-color-primary);
  473. }
  474. }
  475. .contract {
  476. margin-top: 11px;
  477. padding: 11px 12px;
  478. background-color: var(--el-color-primary-light-9);
  479. color: var(--el-color-primary);
  480. font-size: 13px;
  481. border-radius: 8px;
  482. }
  483. }
  484. .scene {
  485. margin: 24px 0;
  486. .title {
  487. margin-bottom: 16px;
  488. }
  489. .scene-item {
  490. position: relative;
  491. padding-left: 20px;
  492. font-weight: 400;
  493. font-size: 14px;
  494. color: #7c7c7c;
  495. padding-bottom: 12px;
  496. &:last-child {
  497. padding-bottom: 0px;
  498. }
  499. }
  500. .timeline-item__tail {
  501. position: absolute;
  502. top: -14px;
  503. left: 6px;
  504. height: 100%;
  505. border-left: 1px dotted var(--el-color-primary);
  506. }
  507. .line::before {
  508. content: '';
  509. position: absolute;
  510. top: 3px;
  511. left: 2px;
  512. width: 10px;
  513. height: 10px;
  514. border-radius: 50%;
  515. background: var(--el-color-primary);
  516. }
  517. }
  518. .btns {
  519. display: grid;
  520. grid-template-columns: repeat(2, 1fr);
  521. gap: 8px;
  522. .el-button {
  523. margin-left: 4px;
  524. margin-bottom: 6px;
  525. padding: 10px 15px !important;
  526. // padding: 0px;
  527. // width: 93px;
  528. }
  529. }
  530. }
  531. }
  532. .right {
  533. display: inline-block;
  534. font-size: 16px;
  535. }
  536. }
  537. </style>
  538. <style lang="scss">
  539. .tab-content {
  540. .el-scrollbar__wrap {
  541. .el-scrollbar__view {
  542. width: 96%;
  543. }
  544. }
  545. }
  546. </style>