EmrSidebar.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  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 name='EmrSidebar'>
  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 store from "@/store";
  71. import {ElIcon, ElMessageBox} from "element-plus";
  72. import {Folder, Document, Open, Sort, View} from "@element-plus/icons-vue";
  73. import {isDev} from "@/utils/public";
  74. import XEUtils from "xe-utils";
  75. import {reqUnlock} from "@/api/emr-control/connotation-quality-control";
  76. const props = defineProps({
  77. maxHeight: {
  78. type: Number
  79. },
  80. huanZheXinXi: {
  81. type: Object
  82. },
  83. patientData: {
  84. type: Object
  85. },
  86. extractData: {
  87. type: Object
  88. },
  89. doctorGrade: {
  90. type: Number
  91. },
  92. isCreate: {
  93. type: Boolean,
  94. default: true
  95. }
  96. })
  97. let editor = emrConfig.value.editor
  98. const emit = defineEmits(['nodeClick', 'typeChange', 'patientMedicalRecord', 'openAndSaveTheMedicalRecord'])
  99. let returnData = $ref({
  100. emrTree: [],
  101. dept: [],
  102. patientTree: []
  103. })
  104. let filterText = $ref('')
  105. let treeRef = $ref('')
  106. let dialog = $ref(false)
  107. let wardList = []
  108. const defaultProps = {
  109. children: 'children',
  110. label: 'name',
  111. }
  112. let templateType = $ref(0)
  113. // 用户信息
  114. let userData = store.state.user.info
  115. const treeData = computed(() => {
  116. switch (templateType) {
  117. case 0 :
  118. return returnData.emrTree;
  119. case 1:
  120. return returnData.deptTree;
  121. case 2:
  122. return returnData.patientTree;
  123. }
  124. })
  125. const wardCreationJudgment = async () => {
  126. if (determineWhetherItCanBeCreated()) {
  127. return
  128. }
  129. if (await judgeTheCreationOfTransfer()) {
  130. return;
  131. }
  132. if (canIUnlockIt(unlockEnum.转科创建病历)) {
  133. return;
  134. }
  135. if (isDev) {
  136. return
  137. }
  138. BizException(ExceptionEnum.MESSAGE_ERROR, "当前患者所在病区不在您的管理病区范围内,无法创建病历。");
  139. }
  140. const handleNodeClick = async (val, node, parent, event) => {
  141. if (val.type === 'group-category') return
  142. let temp = {}
  143. if (templateType === 0 || templateType === 1) {
  144. temp = {
  145. documentId: null,
  146. categoryCode: val.code,
  147. categoryId: val._id,
  148. patientId: null,
  149. name: val.name,
  150. createId: null,
  151. parent: val.parent,
  152. }
  153. await wardCreationJudgment()
  154. } else if (templateType === 2) {
  155. temp = {
  156. documentId: val.emrDocumentId,
  157. categoryCode: val.emrCategoryCode,
  158. categoryId: null,
  159. patientId: null,
  160. name: val.name ? val.name : val.emrName,
  161. createId: val.createId,
  162. createName: val.createName
  163. }
  164. if (val.jump) {
  165. let tempVal = node.parent.data
  166. emrMitt.emit('audit', {
  167. id: tempVal.id,
  168. code: tempVal.emrCategoryCode,
  169. createId: tempVal.createId,
  170. referPhysician: props.huanZheXinXi.referPhysician,
  171. patNo: props.huanZheXinXi.inpatientNo,
  172. times: props.huanZheXinXi.admissTimes
  173. });
  174. } else {
  175. emrMitt.emit('audit', {
  176. id: val.id,
  177. code: val.emrCategoryCode,
  178. createId: val.createId,
  179. referPhysician: props.huanZheXinXi.referPhysician,
  180. patNo: props.huanZheXinXi.inpatientNo,
  181. times: props.huanZheXinXi.admissTimes
  182. });
  183. }
  184. }
  185. if (val.children) {
  186. return
  187. }
  188. if (val.labels) {
  189. // 根据 这个编码来限制是不是唯一一个
  190. if (val.labels.includes('唯一') && templateType !== 2) {
  191. let flag = await queryWhetherThePatientHasASpecifiedMedicalRecord({
  192. patNo: props.huanZheXinXi.inpatientNo,
  193. times: props.huanZheXinXi.admissTimes,
  194. emrCategoryCode: val.code
  195. })
  196. if (flag) {
  197. BizException(ExceptionEnum.LOGICAL_ERROR, '此病历只能创建一次。')
  198. }
  199. }
  200. }
  201. if (val.jump) {
  202. temp.code = val.code
  203. temp.value = val.value
  204. emit('nodeClick', temp, true, templateType);
  205. } else {
  206. emit('nodeClick', temp, false, templateType);
  207. }
  208. }
  209. const determineWhetherItCanBeCreated = () => {
  210. if (userData.code === props.patientData['管床医生编码'] || props.doctorGrade > 1) {
  211. return true;
  212. }
  213. return wardList.includes(props.huanZheXinXi.ward);
  214. }
  215. const judgeTheCreationOfTransfer = async () => {
  216. let res = await whetherItExistsInTheDepartment(props.huanZheXinXi.inpatientNo, props.huanZheXinXi.admissTimes);
  217. if (res === null) {
  218. return false;
  219. }
  220. if (res.timeout) {
  221. return false;
  222. }
  223. return wardList.includes(res.fDeptCode);
  224. }
  225. const typeChange = (val) => {
  226. emit("typeChange", val)
  227. }
  228. const filterChange = (val) => {
  229. try {
  230. hisTreeRef.value.filter(val)
  231. } catch {
  232. }
  233. try {
  234. treeRef.filter(val);
  235. } catch {
  236. }
  237. }
  238. const filterNode = (value, data) => {
  239. if (!value) return true
  240. return data.name.includes(value)
  241. }
  242. const changeTemplateType = (val) => {
  243. templateType = val
  244. typeChange(val)
  245. }
  246. const deleteTheSpecifiedNode = (id) => {
  247. for (let i = 0, len = returnData.patientTree.length; i < len; i++) {
  248. let item = returnData.patientTree[i]
  249. if (item.emrDocumentId === id) {
  250. returnData.patientTree.splice(i, 1)
  251. return
  252. }
  253. if (item.children) {
  254. for (let j = 0; j < item.children.length; j++) {
  255. let child = item.children[j]
  256. if (child.emrDocumentId === id) {
  257. item.children.splice(i, 1)
  258. return
  259. }
  260. }
  261. }
  262. }
  263. }
  264. let findNode = false
  265. const diseaseDurationRecordTime = (id, list) => {
  266. findNode = false;
  267. findMedicalRecordById(id, list, returnData.patientTree);
  268. }
  269. const findMedicalRecordById = (id, roundTimes, list) => {
  270. for (let i = 0, len = list.length; i < len; i++) {
  271. if (findNode) return;
  272. let item = list[i]
  273. if (item.emrDocumentId === id) {
  274. findNode = true
  275. item.type = 'group-category'
  276. item.children = roundTimes
  277. return
  278. }
  279. if (item.children) {
  280. findMedicalRecordById(id, roundTimes, item.children)
  281. }
  282. }
  283. }
  284. const queryData = () => {
  285. getPatientDataTree(props.huanZheXinXi.inpatientNo, props.huanZheXinXi.admissTimes).then((res) => {
  286. if (res?.length > 0) {
  287. templateType = 2
  288. emit('patientMedicalRecord')
  289. }
  290. returnData.patientTree = res
  291. })
  292. }
  293. const hisData = ref([])
  294. const hisTreeRef = ref(null)
  295. const pastHistory = () => {
  296. getPastHistory(props.huanZheXinXi.inpatientNo, props.huanZheXinXi.admissTimes).then((res) => {
  297. let temp = []
  298. for (let key in res) {
  299. temp.push({
  300. name: `第${key}次住院`,
  301. children: res[key]
  302. })
  303. }
  304. hisData.value = temp
  305. })
  306. }
  307. const hisClick = (val) => {
  308. if (!val.emrDocumentId) return
  309. emrMitt.emit('setShowIframe', 3, val.emrDocumentId)
  310. }
  311. const fileName = (val) => {
  312. if (val.jump) {
  313. let tempDate = val.trueCreationTime
  314. if (!tempDate) {
  315. tempDate = val.createDate
  316. }
  317. return val.name + nullToEmpty(val.createName) + nullToEmpty(tempDate)
  318. }
  319. if (templateType === 2) {
  320. return val.name + nullToEmpty(val.createName) + nullToEmpty(val.createDate)
  321. } else {
  322. return val.name
  323. }
  324. }
  325. const nullToEmpty = (val) => {
  326. return stringIsBlank(val) ? '' : ' \\ ' + val
  327. }
  328. const mousePosition = ref()
  329. const opt = [
  330. {
  331. name: '打开(只读)', click: (data) => {
  332. if (!data.unlock.id) {
  333. xcMessage.error('请选中保存的病历。')
  334. return
  335. }
  336. emit('openAndSaveTheMedicalRecord', data.unlock.id)
  337. },
  338. icon: h(ElIcon, {}, () => h(Open))
  339. },
  340. {
  341. name: '同时打开', click: (data) => {
  342. if (!data.unlock.id) {
  343. xcMessage.error('请选中保存的病历。')
  344. return
  345. }
  346. emit('openAndSaveTheMedicalRecord', data.unlock.id, 3)
  347. },
  348. icon: h(ElIcon, {}, () => h(View))
  349. },
  350. {
  351. name: '申请解锁编辑', click: ({unlock}) => {
  352. if (!unlock.id) {
  353. return xcMessage.error('请选择病历')
  354. }
  355. ElMessageBox.prompt('申请编辑病历', '提示', {
  356. type: "warning",
  357. closeOnPressEscape: false,
  358. closeOnClickModal: false,
  359. inputValidator: (val) => {
  360. if (XEUtils.isEmpty(val)) {
  361. return false;
  362. }
  363. if (val === null || val.length < 1 || val.length > 50) {
  364. return false;
  365. }
  366. },
  367. dangerouslyUseHTMLString: true,
  368. inputErrorMessage: '不能为空,最多可输入50个字符。',
  369. }).then(({value}) => {
  370. let data = {
  371. inputRemark: value,
  372. code: '',
  373. patNo: unlock.patNo,
  374. times: unlock.times,
  375. flag: 0,
  376. emrId: unlock.id
  377. }
  378. reqUnlock(data)
  379. }).catch(() => {
  380. })
  381. },
  382. validator: () => {
  383. return false
  384. },
  385. },
  386. {
  387. name: '确认排序', click: (data) => {
  388. let temp = []
  389. drag.forEach((value, key) => {
  390. if (value.newParent !== value.oldParent) {
  391. temp.push({id: key, parent: value.newParent})
  392. }
  393. })
  394. if (temp.length === 0) {
  395. drag.clear()
  396. return xcMessage.error('文件夹没有变化,无需点击。')
  397. }
  398. electronicMedicalRecordSequencing(temp).then(() => {
  399. drag.clear()
  400. })
  401. },
  402. validator: () => {
  403. return drag.size > 0
  404. },
  405. icon: h(ElIcon, {}, () => h(Sort))
  406. }
  407. ]
  408. const contextmenuItem = (event, data) => {
  409. let tempData;
  410. if (data.emrDocumentId) {
  411. tempData = {
  412. id: data.emrDocumentId,
  413. code: data.emrCategoryCode,
  414. patNo: data.patNo,
  415. times: data.times,
  416. name: data.name ? data.name : data.emrName,
  417. }
  418. } else {
  419. tempData = {
  420. id: null,
  421. code: data.code,
  422. patNo: props.huanZheXinXi.inpatientNo,
  423. times: props.huanZheXinXi.admissTimes,
  424. name: data.name,
  425. }
  426. }
  427. event.returnValue = false;
  428. mousePosition.value = {
  429. event,
  430. index: 0,
  431. data: {
  432. unlock: tempData,
  433. original: data
  434. }
  435. };
  436. }
  437. const isItAFolder = (data) => {
  438. return h(ElIcon, null,
  439. {
  440. default: () => {
  441. if (data.type === 'category') {
  442. return h(Document)
  443. } else {
  444. return h(Folder)
  445. }
  446. }
  447. }
  448. )
  449. }
  450. const dragLimit = (moveNode, inNode, type) => {
  451. if (templateType !== 2) return false;
  452. if (moveNode.data.jump) return false;
  453. if (inNode.data._id && type === 'inner') {
  454. return true;
  455. }
  456. }
  457. const allowDrag = (node) => {
  458. if (templateType !== 2) return false;
  459. if (node.data.emrCategoryCode === 'shoucibingchengjilu') {
  460. return true;
  461. }
  462. return node.data.type !== "group-category";
  463. }
  464. let drag = new Map();
  465. const dragEnd = (moveNode, inNode, type, event) => {
  466. if (type === 'none') return
  467. let temp = {
  468. newParent: inNode.data._id,
  469. oldParent: moveNode.data.parent
  470. }
  471. drag.set(moveNode.data.id, temp)
  472. }
  473. onMounted(() => {
  474. emrMitt.on('患者病区判断', () => {
  475. return determineWhetherItCanBeCreated()
  476. })
  477. pastHistory()
  478. queryData()
  479. if (editor) {
  480. getAllWards().then((res) => {
  481. if (res.length > 0) {
  482. for (let i = 0, len = res.length; i < len; i++) {
  483. wardList.push(res[i].code)
  484. }
  485. }
  486. })
  487. getEmrTree().then((res) => {
  488. returnData.emrTree = res.all
  489. returnData.deptTree = res.dept
  490. })
  491. } else {
  492. templateType = 2
  493. }
  494. })
  495. defineExpose({
  496. changeTemplateType,
  497. deleteTheSpecifiedNode,
  498. diseaseDurationRecordTime,
  499. queryData
  500. })
  501. </script>
  502. <style scoped lang="scss">
  503. .down-tree {
  504. :deep(.el-tree-node.is-expanded > .el-tree-node__children) {
  505. display: inline;
  506. }
  507. }
  508. </style>