EmrSidebar.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. <template>
  2. <div>
  3. <el-input v-model="filterText"
  4. placeholder="节点过滤"
  5. style="width: 100%"
  6. @input="filterChange" clearable/>
  7. <el-radio-group v-model="templateType" @change="typeChange">
  8. <el-radio-button v-if="props.isCreate" :disabled="!editor" :label="0">全院</el-radio-button>
  9. <el-radio-button v-if="props.isCreate" :disabled="!editor" :label="1">科室</el-radio-button>
  10. <el-radio-button :label="2">当前</el-radio-button>
  11. <el-radio-button :label="3">历史</el-radio-button>
  12. </el-radio-group>
  13. <div :style="{maxHeight : maxHeight - 100 + 'px'}"
  14. style="overflow: auto; "
  15. class="down-tree">
  16. <el-tree v-show="templateType === 3"
  17. :props="defaultProps"
  18. ref="hisTreeRef"
  19. :filter-node-method="filterNode"
  20. :data="hisData"
  21. @node-click="hisClick">
  22. </el-tree>
  23. <el-tree v-show="templateType !== 3"
  24. :data="treeData"
  25. :props="defaultProps"
  26. @node-click="handleNodeClick"
  27. node-key="_id"
  28. ref="treeRef"
  29. draggable
  30. @node-drag-end="dragEnd"
  31. :allow-drag="allowDrag"
  32. :allow-drop="dragLimit"
  33. @node-contextmenu="contextmenuItem"
  34. highlight-current
  35. :filter-node-method="filterNode"
  36. default-expand-all>
  37. <template #default="{ node, data }">
  38. <el-icon v-if="data.submit">
  39. <Lock/>
  40. </el-icon>
  41. <component :is="isItAFolder(data)"/>
  42. <span :title="fileName(data)">
  43. {{ fileName(data) }}
  44. </span>
  45. </template>
  46. </el-tree>
  47. </div>
  48. <right-click-menu :mouse-position="mousePosition" :config="opt"/>
  49. </div>
  50. </template>
  51. <script setup lang="ts">
  52. import {
  53. electronicMedicalRecordSequencing,
  54. getEmrTree, getPastHistory,
  55. getPatientDataTree,
  56. queryWhetherThePatientHasASpecifiedMedicalRecord,
  57. whetherItExistsInTheDepartment
  58. } from "@/api/zhu-yuan-yi-sheng/emr-patient";
  59. import {BizException, ExceptionEnum} from "@/utils/BizException";
  60. import {
  61. canIUnlockIt,
  62. emrConfig,
  63. emrMitt,
  64. unlockEnum
  65. } from '@/views/hospitalization/zhu-yuan-yi-sheng/electronic-medical-record/emr-editor/emr-init'
  66. import {stringIsBlank} from "@/utils/blank-utils";
  67. import {getAllWards} from "@/api/zhu-yuan-yi-sheng/resident-doctor";
  68. import RightClickMenu from "@/components/menu-item/RightClickMenu.vue";
  69. import {xcMessage} from "@/utils/xiaochan-element-plus";
  70. import {ElIcon, ElTree} from "element-plus";
  71. // @ts-ignore
  72. import {Folder, Document, Open, Sort, View} from "@element-plus/icons-vue";
  73. import {isDev} from "@/utils/public";
  74. import {ref, computed, h, onMounted} from 'vue'
  75. import {useCompRef} from "@/utils/useCompRef";
  76. import {userInfoStore} from "@/utils/store-public";
  77. const props = defineProps({
  78. maxHeight: {
  79. type: Number
  80. },
  81. huanZheXinXi: {
  82. type: Object
  83. },
  84. patientData: {
  85. type: Object
  86. },
  87. extractData: {
  88. type: Object
  89. },
  90. doctorGrade: {
  91. type: Number
  92. },
  93. isCreate: {
  94. type: Boolean,
  95. default: true
  96. }
  97. })
  98. let editor = emrConfig.value.editor
  99. const emit = defineEmits(['nodeClick', 'typeChange', 'patientMedicalRecord', 'openAndSaveTheMedicalRecord'])
  100. const returnData = ref({
  101. emrTree: [],
  102. deptTree: [],
  103. patientTree: []
  104. })
  105. const filterText = ref('')
  106. const treeRef = useCompRef(ElTree)
  107. const dialog = ref(false)
  108. let wardList = []
  109. const defaultProps = {
  110. children: 'children',
  111. label: 'name',
  112. }
  113. const templateType = ref(0)
  114. const treeData = computed(() => {
  115. switch (templateType.value) {
  116. case 0 :
  117. return returnData.value.emrTree;
  118. case 1:
  119. return returnData.value.deptTree;
  120. case 2:
  121. return returnData.value.patientTree;
  122. }
  123. })
  124. const wardCreationJudgment = async () => {
  125. if (determineWhetherItCanBeCreated()) {
  126. return
  127. }
  128. if (await judgeTheCreationOfTransfer()) {
  129. return;
  130. }
  131. if (canIUnlockIt(unlockEnum.转科创建病历)) {
  132. return;
  133. }
  134. if (isDev) {
  135. return
  136. }
  137. BizException(ExceptionEnum.MESSAGE_ERROR, "当前患者所在病区不在您的管理病区范围内,无法创建病历。");
  138. }
  139. const handleNodeClick = async (val, node, parent, event) => {
  140. if (val.type === 'group-category') return
  141. let temp: {
  142. code?: string
  143. value?: string
  144. // 电子病历的 id 如果是新建就没有
  145. documentId?: string | null
  146. categoryId?: string | null
  147. // 电子病历的 编码
  148. categoryCode?: string
  149. // 患者的 id
  150. patientId?: string | null
  151. // 电子病历 名称
  152. name?: string
  153. // 创建人 id
  154. createId?: string | null
  155. // 父级节点
  156. parent?: string
  157. createName?: string
  158. } = {}
  159. if (templateType.value === 0 || templateType.value === 1) {
  160. temp = {
  161. documentId: null,
  162. categoryCode: val.code,
  163. categoryId: val._id,
  164. patientId: null,
  165. name: val.name,
  166. createId: null,
  167. parent: val.parent,
  168. }
  169. await wardCreationJudgment()
  170. } else if (templateType.value === 2) {
  171. temp = {
  172. documentId: val.emrDocumentId,
  173. categoryCode: val.emrCategoryCode,
  174. categoryId: null,
  175. patientId: null,
  176. name: val.name ? val.name : val.emrName,
  177. createId: val.createId,
  178. createName: val.createName
  179. }
  180. if (val.jump) {
  181. let tempVal = node.parent.data
  182. emrMitt.emit('audit', {
  183. id: tempVal.id,
  184. code: tempVal.emrCategoryCode,
  185. createId: tempVal.createId,
  186. referPhysician: props.huanZheXinXi.referPhysician,
  187. patNo: props.huanZheXinXi.inpatientNo,
  188. times: props.huanZheXinXi.admissTimes
  189. });
  190. } else {
  191. emrMitt.emit('audit', {
  192. id: val.id,
  193. code: val.emrCategoryCode,
  194. createId: val.createId,
  195. referPhysician: props.huanZheXinXi.referPhysician,
  196. patNo: props.huanZheXinXi.inpatientNo,
  197. times: props.huanZheXinXi.admissTimes
  198. });
  199. }
  200. }
  201. if (val.children) {
  202. return
  203. }
  204. if (val.labels) {
  205. // 根据 这个编码来限制是不是唯一一个
  206. if (val.labels.includes('唯一') && templateType.value !== 2) {
  207. let flag = await queryWhetherThePatientHasASpecifiedMedicalRecord({
  208. patNo: props.huanZheXinXi.inpatientNo,
  209. times: props.huanZheXinXi.admissTimes,
  210. emrCategoryCode: val.code
  211. })
  212. if (flag) {
  213. BizException(ExceptionEnum.LOGICAL_ERROR, '此病历只能创建一次。')
  214. }
  215. }
  216. }
  217. if (val.jump) {
  218. temp.code = val.code
  219. temp.value = val.value
  220. emit('nodeClick', temp, true, templateType.value);
  221. } else {
  222. emit('nodeClick', temp, false, templateType.value);
  223. }
  224. }
  225. const determineWhetherItCanBeCreated = () => {
  226. if (userInfoStore.value.code === props.patientData['管床医生编码'] || props.doctorGrade > 1) {
  227. return true;
  228. }
  229. return wardList.includes(props.huanZheXinXi.ward);
  230. }
  231. const judgeTheCreationOfTransfer = async () => {
  232. let res = await whetherItExistsInTheDepartment(props.huanZheXinXi.inpatientNo, props.huanZheXinXi.admissTimes) as any;
  233. if (res === null) {
  234. return false;
  235. }
  236. if (res.timeout) {
  237. return false;
  238. }
  239. return wardList.includes(res.fDeptCode);
  240. }
  241. const typeChange = (val) => {
  242. emit("typeChange", val)
  243. }
  244. const filterChange = (val) => {
  245. try {
  246. hisTreeRef.value.filter(val)
  247. } catch {
  248. }
  249. try {
  250. treeRef.value.filter(val);
  251. } catch {
  252. }
  253. }
  254. const filterNode = (value, data) => {
  255. if (!value) return true
  256. return data.name.includes(value)
  257. }
  258. const changeTemplateType = (val) => {
  259. templateType.value = val
  260. typeChange(val)
  261. }
  262. const deleteTheSpecifiedNode = (id) => {
  263. for (let i = 0, len = returnData.value.patientTree.length; i < len; i++) {
  264. let item = returnData.value.patientTree[i]
  265. if (item.emrDocumentId === id) {
  266. returnData.value.patientTree.splice(i, 1)
  267. return
  268. }
  269. if (item.children) {
  270. for (let j = 0; j < item.children.length; j++) {
  271. let child = item.children[j]
  272. if (child.emrDocumentId === id) {
  273. item.children.splice(i, 1)
  274. return
  275. }
  276. }
  277. }
  278. }
  279. }
  280. let findNode = false
  281. const diseaseDurationRecordTime = (id, list) => {
  282. findNode = false;
  283. findMedicalRecordById(id, list, returnData.value.patientTree);
  284. }
  285. const findMedicalRecordById = (id, roundTimes, list) => {
  286. for (let i = 0, len = list.length; i < len; i++) {
  287. if (findNode) return;
  288. let item = list[i]
  289. if (item.emrDocumentId === id) {
  290. findNode = true
  291. item.type = 'group-category'
  292. item.children = roundTimes
  293. return
  294. }
  295. if (item.children) {
  296. findMedicalRecordById(id, roundTimes, item.children)
  297. }
  298. }
  299. }
  300. const queryData = () => {
  301. getPatientDataTree(props.huanZheXinXi.inpatientNo, props.huanZheXinXi.admissTimes).then((res) => {
  302. if (res?.length > 0) {
  303. templateType.value = 2
  304. emit('patientMedicalRecord')
  305. }
  306. returnData.value.patientTree = res
  307. })
  308. }
  309. const hisData = ref([])
  310. const hisTreeRef = ref(null)
  311. const pastHistory = () => {
  312. getPastHistory(props.huanZheXinXi.inpatientNo, props.huanZheXinXi.admissTimes).then((res) => {
  313. let temp = []
  314. for (let key in res) {
  315. temp.push({
  316. name: `第${key}次住院`,
  317. children: res[key]
  318. })
  319. }
  320. hisData.value = temp
  321. })
  322. }
  323. const hisClick = (val) => {
  324. if (!val.emrDocumentId) return
  325. emrMitt.emit('setShowIframe', 3, val.emrDocumentId)
  326. }
  327. const fileName = (val) => {
  328. if (val.jump) {
  329. let tempDate = val.trueCreationTime
  330. if (!tempDate) {
  331. tempDate = val.createDate
  332. }
  333. return val.name + nullToEmpty(val.createName) + nullToEmpty(tempDate)
  334. }
  335. if (templateType.value === 2) {
  336. return val.name + nullToEmpty(val.createName) + nullToEmpty(val.createDate)
  337. } else {
  338. return val.name
  339. }
  340. }
  341. const nullToEmpty = (val) => {
  342. return stringIsBlank(val) ? '' : ' \\ ' + val
  343. }
  344. const mousePosition = ref()
  345. const opt = [
  346. {
  347. name: '打开(只读)', click: (data) => {
  348. if (!data.unlock.id) {
  349. xcMessage.error('请选中保存的病历。')
  350. return
  351. }
  352. emit('openAndSaveTheMedicalRecord', data.unlock.id)
  353. },
  354. icon: h(ElIcon, {}, () => h(Open))
  355. },
  356. {
  357. name: '同时打开', click: (data) => {
  358. if (!data.unlock.id) {
  359. xcMessage.error('请选中保存的病历。')
  360. return
  361. }
  362. emit('openAndSaveTheMedicalRecord', data.unlock.id, 3)
  363. },
  364. icon: h(ElIcon, {}, () => h(View))
  365. },
  366. {
  367. name: '确认排序', click: (data) => {
  368. let temp = []
  369. drag.forEach((value, key) => {
  370. if (value.newParent !== value.oldParent) {
  371. temp.push({id: key, parent: value.newParent})
  372. }
  373. })
  374. if (temp.length === 0) {
  375. drag.clear()
  376. return xcMessage.error('文件夹没有变化,无需点击。')
  377. }
  378. electronicMedicalRecordSequencing(temp).then(() => {
  379. drag.clear()
  380. })
  381. },
  382. validator: () => {
  383. return drag.size > 0
  384. },
  385. icon: h(ElIcon, {}, () => h(Sort))
  386. }
  387. ]
  388. const contextmenuItem = (event, data) => {
  389. let tempData;
  390. if (data.emrDocumentId) {
  391. tempData = {
  392. id: data.emrDocumentId,
  393. code: data.emrCategoryCode,
  394. patNo: data.patNo,
  395. times: data.times,
  396. name: data.name ? data.name : data.emrName,
  397. }
  398. } else {
  399. tempData = {
  400. id: null,
  401. code: data.code,
  402. patNo: props.huanZheXinXi.inpatientNo,
  403. times: props.huanZheXinXi.admissTimes,
  404. name: data.name,
  405. }
  406. }
  407. event.returnValue = false;
  408. mousePosition.value = {
  409. event,
  410. index: 0,
  411. data: {
  412. unlock: tempData,
  413. original: data
  414. }
  415. };
  416. }
  417. const isItAFolder = (data) => {
  418. return h(ElIcon, null,
  419. {
  420. default: () => {
  421. if (data.type === 'category') {
  422. return h(Document)
  423. } else {
  424. return h(Folder)
  425. }
  426. }
  427. }
  428. )
  429. }
  430. const dragLimit = (moveNode, inNode, type) => {
  431. if (templateType.value !== 2) return false;
  432. if (moveNode.data.jump) return false;
  433. if (inNode.data._id && type === 'inner') {
  434. return true;
  435. }
  436. }
  437. const allowDrag = (node) => {
  438. if (templateType.value !== 2) return false;
  439. if (node.data.emrCategoryCode === 'shoucibingchengjilu') {
  440. return true;
  441. }
  442. return node.data.type !== "group-category";
  443. }
  444. let drag = new Map();
  445. const dragEnd = (moveNode, inNode, type, event) => {
  446. if (type === 'none') return
  447. let temp = {
  448. newParent: inNode.data._id,
  449. oldParent: moveNode.data.parent
  450. }
  451. drag.set(moveNode.data.id, temp)
  452. }
  453. onMounted(() => {
  454. emrMitt.on('患者病区判断', () => {
  455. return determineWhetherItCanBeCreated()
  456. })
  457. emrMitt.on('querySidebar', queryData)
  458. pastHistory()
  459. queryData()
  460. if (editor) {
  461. getAllWards().then((res) => {
  462. if (res.length > 0) {
  463. for (let i = 0, len = res.length; i < len; i++) {
  464. wardList.push(res[i].code)
  465. }
  466. }
  467. })
  468. getEmrTree().then((res) => {
  469. returnData.value.emrTree = res.all
  470. returnData.value.deptTree = res.dept
  471. })
  472. } else {
  473. templateType.value = 2
  474. }
  475. })
  476. defineExpose({
  477. changeTemplateType,
  478. deleteTheSpecifiedNode,
  479. diseaseDurationRecordTime,
  480. queryData
  481. })
  482. </script>
  483. <style scoped lang="scss">
  484. .down-tree {
  485. :deep(.el-tree-node.is-expanded > .el-tree-node__children) {
  486. display: inline;
  487. }
  488. }
  489. </style>