YzTableV3.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. <script setup lang="ts">
  2. import {stringIsBlank, stringNotBlank} from "@/utils/blank-utils";
  3. import {
  4. associateOrders,
  5. clearAssociate,
  6. jsQueryYzData,
  7. openDrugManual,
  8. OrderBy,
  9. queryParam,
  10. tempYzData,
  11. yiZhuData,
  12. yzMitt,
  13. YzType
  14. } from "@/views/hospitalization/zhu-yuan-yi-sheng/public-js/zhu-yuan-yi-sheng";
  15. import {h, nextTick, reactive, ref} from 'vue'
  16. import {VxeTable, VxeTablePropTypes} from 'vxe-table'
  17. import {nullToEmpty} from '@/utils/public'
  18. import {getFormatDatetime} from '@/utils/date'
  19. import {getServerDateApi} from '@/api/public-api'
  20. import {updateOrderInstruction} from '@/api/zhu-yuan-yi-sheng/yi-zhu-lu-ru'
  21. import sleep from "../../../../../../utils/sleep";
  22. import {ElButton, ElButtonGroup} from "element-plus";
  23. import {xcMessage} from "@/utils/xiaochan-element-plus";
  24. import XEUtils from "xe-utils";
  25. import {TablePublicMethods} from "vxe-table/types/table";
  26. const props = defineProps({
  27. height: {
  28. type: [Number]
  29. },
  30. width: {
  31. type: [Number]
  32. }
  33. })
  34. const emits = defineEmits(['rowClick', 'voidOrders'])
  35. const tableRef = ref<TablePublicMethods>()
  36. const toggleAllCheckboxEvent = () => {
  37. const $table = tableRef.value
  38. if ($table) {
  39. $table!.toggleAllCheckboxRow()
  40. }
  41. }
  42. const toggleCheckboxEvent = (row) => {
  43. const $table = tableRef.value
  44. if ($table) {
  45. $table!.toggleCheckboxRow(row)
  46. }
  47. }
  48. interface MenuClick {
  49. $event: Event,
  50. row: YzType,
  51. menu: { code: string, name: string, disabled?: boolean },
  52. column: any
  53. }
  54. // 鼠标右键配置文件
  55. const menuConfig = reactive<VxeTablePropTypes.MenuConfig>({
  56. body: {
  57. options: [
  58. [
  59. {code: 'fee', name: '医嘱费用'}
  60. ],
  61. [
  62. {code: 'copy', name: '复制', prefixIcon: 'vxe-icon-copy'}
  63. ],
  64. [
  65. {
  66. code: 'paste',
  67. name: '粘贴',
  68. prefixIcon: 'vxe-icon-paste'
  69. }
  70. ],
  71. [
  72. {code: 'relevancy', name: '关联'}
  73. ],
  74. [
  75. {code: 'exitTheAssociation', name: '退出关联'}
  76. ],
  77. [
  78. {
  79. code: 'instructions',
  80. name: '说明书'
  81. }
  82. ],
  83. [
  84. {
  85. code: 'stopTime',
  86. name: '停止医嘱'
  87. }
  88. ]
  89. ],
  90. },
  91. visibleMethod({options, column, row}) {
  92. options.forEach(list => {
  93. list.forEach((item) => {
  94. item.disabled = true
  95. if (item.code === 'paste') {
  96. item.disabled = !yzMitt.emit('allowReplication')
  97. }
  98. if (row) {
  99. if (['copy', 'relevancy', 'exitTheAssociation'].includes(item.code)) {
  100. item.disabled = false
  101. }
  102. if (['relevancy', 'instructions'].includes(item.code)) {
  103. item.disabled = row.serial == '00'
  104. }
  105. if (item.code === 'fee') {
  106. item.disabled = false
  107. }
  108. if (item.code === 'stopTime') {
  109. item.disabled = !showEndTime(row)
  110. }
  111. }
  112. })
  113. })
  114. return true
  115. }
  116. })
  117. // 鼠标右键执行的函数
  118. const rightFunc = {
  119. "fee": (data: YzType) => {
  120. yzMitt.emit('queryFeeByOrderNo', data)
  121. },
  122. "copy": (data: YzType) => {
  123. yzMitt.emit('copy', data.actOrderNo, data.frequCode)
  124. },
  125. "paste": (data: YzType) => {
  126. yzMitt.emit('paste')
  127. },
  128. "relevancy": async (data: YzType) => {
  129. if (canBeAssociated(data, true)) {
  130. if (associateOrders.value.actOrderNo === null) {
  131. await jsQueryYzData()
  132. associateOrders.value.actOrderNo = data.actOrderNo
  133. } else {
  134. xcMessage.error('请先确认当前关联医嘱。')
  135. }
  136. }
  137. },
  138. "exitTheAssociation": (data: YzType) => {
  139. clearAssociate()
  140. },
  141. "instructions": (data: YzType) => {
  142. openDrugManual(data.orderCode, data.serial)
  143. },
  144. "stopTime": (data) => {
  145. if (tableRef.value.isCheckedByCheckboxRow(data)) {
  146. cancelStopTime(data)
  147. } else {
  148. setDefaultStopTime({row: data})
  149. }
  150. }
  151. }
  152. // 触发鼠标右键的事件
  153. const tableRightClick = (val: MenuClick) => {
  154. let {row, menu} = val
  155. try {
  156. rightFunc[menu.code](row)
  157. } catch {
  158. }
  159. }
  160. function canBeAssociated(data: YzType, showMessage = false) {
  161. function message(message) {
  162. showMessage && xcMessage.error(message);
  163. }
  164. if (stringNotBlank(data.parentNo)) {
  165. message('该医嘱已经有父医嘱了。');
  166. return false
  167. } else if (data.statusFlag !== '1') {
  168. message('不是录入状态的医嘱无法关联。');
  169. return false
  170. } else if (data.serial === '00') {
  171. message('项目无法关联。');
  172. return false
  173. }
  174. return true
  175. }
  176. const twinkleList = ref({})
  177. const rowClassName = ({row, rowIndex}) => {
  178. let data = row as YzType
  179. if (typeof twinkleList.value[data.actOrderNo] !== 'undefined') {
  180. sleep(3000).then(() => {
  181. delete twinkleList.value[data.actOrderNo]
  182. })
  183. return 'animation_hzfirst'
  184. } else if (typeof twinkleList.value[data.parentNo] !== 'undefined') {
  185. return 'animation_hzfirst'
  186. }
  187. // 父级
  188. if (data.associationFlag) {
  189. return 'parent_level'
  190. }
  191. // 子级
  192. if (associateOrders.value.actOrderNo === data.actOrderNo) {
  193. return 'child_level'
  194. }
  195. if (data.actOrderNo === yiZhuData.value.actOrderNo) {
  196. return 'activation'
  197. }
  198. }
  199. const setDefaultStopTime = async (val) => {
  200. let {row} = val
  201. if (showEndTime(row)) {
  202. row.endTimeTemp = getFormatDatetime(await getServerDateApi(), 'YYYY-MM-DDTHH:mm')
  203. await tableRef.value.setCheckboxRow(row, true)
  204. }
  205. }
  206. const endTimeChange = (val, row) => {
  207. if (stringIsBlank(val)) {
  208. tableRef.value.setCheckboxRow(row, false)
  209. } else {
  210. tableRef.value.setCheckboxRow(row, true)
  211. }
  212. }
  213. const cancelStopTime = (row) => {
  214. if (row.parentNo) return
  215. row.endTimeTemp = ''
  216. tableRef.value.setCheckboxRow(row, false)
  217. }
  218. const instructionEnter = (row: YzType) => {
  219. updateOrderInstruction(row.actOrderNo, row.instruction)
  220. }
  221. function getYiZhuFlag(val) {
  222. if (stringIsBlank(val)) {
  223. return val
  224. }
  225. switch (val) {
  226. case '1':
  227. return h('span', {style: {color: '#05ff00'}}, 'R')
  228. case '2':
  229. return h('span', {style: {color: '#0000fb'}}, 'Q')
  230. case '3':
  231. return h('span', {style: {color: '#ff07f3'}}, 'Z')
  232. case '4':
  233. return h('span', {style: {color: '#ff07f3'}}, 'Z')
  234. case '5':
  235. return h('span', {style: {color: 'red'}}, 'T')
  236. case '-1':
  237. return h('span', {style: {color: '#00ffe0'}}, 'D')
  238. default:
  239. return 'warning'
  240. }
  241. }
  242. const timeFomat = (val) => {
  243. return getFormatDatetime(val, 'YY-MM-DD HH:mm')
  244. }
  245. const showEndTime = (data: YzType) => {
  246. if (!data) {
  247. return false
  248. }
  249. return stringIsBlank(data.endTime) && stringIsBlank(data.parentNo) && data.frequCode !== 'ONCE';
  250. }
  251. const rowClick = ({row}) => {
  252. emits('rowClick', row)
  253. }
  254. const endDateStyle = (item) => {
  255. if (item.endTimeTemp && tableRef.value!.isCheckedByCheckboxRow(item)) {
  256. return {
  257. width: '140px',
  258. color: 'white',
  259. backgroundColor: 'red',
  260. border: 0,
  261. }
  262. } else {
  263. return {
  264. width: '140px',
  265. border: 0,
  266. }
  267. }
  268. }
  269. function groupConversion(value: string) {
  270. if (queryParam.value.sort === OrderBy.desc) {
  271. switch (value) {
  272. case '┌':
  273. return "└";
  274. case '└':
  275. return "┌";
  276. case "丨":
  277. return "丨"
  278. default:
  279. return value;
  280. }
  281. }
  282. return value
  283. }
  284. yzMitt.on('tableScroll', (val) => {
  285. tableRef.value!.scrollTo(0, val)
  286. })
  287. yzMitt.on('clearSelected', () => {
  288. tableRef.value!.clearSelected()
  289. })
  290. yzMitt.on('scrollEndAndTwinkle', async (val: any) => {
  291. twinkleList.value = val;
  292. await nextTick()
  293. await sleep(500)
  294. if (queryParam.value.sort === OrderBy.asc) {
  295. const tmp = tableRef.value!.getData()
  296. await tableRef.value!.scrollTo(0, tmp.length * 24)
  297. } else {
  298. await tableRef.value?.scrollTo(0, 0);
  299. }
  300. })
  301. yzMitt.on('setOrderNoTwinkle', async (val: number) => {
  302. twinkleList.value[val] = true;
  303. await nextTick()
  304. await sleep(500)
  305. if (queryParam.value.sort === OrderBy.asc) {
  306. const tmp = tableRef.value!.getData()
  307. tableRef.value!.scrollTo(0, tmp.length * 24)
  308. } else {
  309. await tableRef.value?.scrollTo(0, 0);
  310. }
  311. })
  312. yzMitt.on('getSelectedData', () => {
  313. return XEUtils.clone(tableRef.value!.getCheckboxRecords(), true) as YzType[]
  314. })
  315. </script>
  316. <template>
  317. <vxe-table
  318. border
  319. height="auto"
  320. sync-resize
  321. auto-resize
  322. :menu-config="menuConfig"
  323. @menu-click="tableRightClick"
  324. @cell-dblclick="setDefaultStopTime"
  325. @cell-click="rowClick"
  326. :scroll-x="{gt: 40,enabled: false}"
  327. :scroll-y="{gt: 0,enabled: true}"
  328. :column-config="{resizable: true}"
  329. :row-config="{height: 24, isHover: true, keyField:'actOrderNo'}"
  330. class="vxe-padding_zero vxe-header-max_content hl-style"
  331. header-row-class-name="padding_zero"
  332. show-header-overflow
  333. show-overflow
  334. :row-class-name="rowClassName"
  335. ref="tableRef"
  336. :data="tempYzData">
  337. <vxe-column type="checkbox" width="20">
  338. <template #header="{ checked, indeterminate }">
  339. <span class="custom-checkbox" @click.stop="toggleAllCheckboxEvent">
  340. <i v-if="indeterminate" class="vxe-icon-square-minus-fill"></i>
  341. <i v-else-if="checked" class="vxe-icon-square-checked-fill"></i>
  342. <i v-else class="vxe-icon-checkbox-unchecked"></i>
  343. </span>
  344. </template>
  345. <template #checkbox="{ row, checked, indeterminate }">
  346. <span class="custom-checkbox" @click.stop="toggleCheckboxEvent(row)">
  347. <i v-if="indeterminate" class="vxe-icon-square-minus-fill"></i>
  348. <i v-else-if="checked" class="vxe-icon-square-checked-fill"></i>
  349. <i v-else class="vxe-icon-checkbox-unchecked"></i>
  350. </span>
  351. </template>
  352. </vxe-column>
  353. <vxe-column type="seq" width="30"/>
  354. <vxe-column field="orderGroup" title="组" width="20">
  355. <template #default={row}>
  356. <span>{{ groupConversion(row['orderGroup']) }}</span>
  357. </template>
  358. </vxe-column>
  359. <vxe-column field="statusFlag" width="20">
  360. <template #default="scope">
  361. <component :is="getYiZhuFlag(scope.row.statusFlag)"/>
  362. </template>
  363. </vxe-column>
  364. <vxe-column field="actOrderNo" title="医嘱号" width="70"/>
  365. <vxe-column field="orderName" title="医嘱名称" width="225"/>
  366. <vxe-column field="dose" title="剂量" width="65">
  367. <template #default="{row}">
  368. {{ nullToEmpty(row.dose) + ' ' + nullToEmpty(row.doseUnitName) }}
  369. </template>
  370. </vxe-column>
  371. <vxe-column field="frequCode" title="频率" width="75"/>
  372. <vxe-column field="supplyCodeName" title="给药方式" width="60"/>
  373. <vxe-column field="startTime" title="开始时间" width="100">
  374. <template #default="{row}">
  375. {{ timeFomat(row.startTime) }}
  376. </template>
  377. </vxe-column>
  378. <vxe-column field="endTime" title="结束时间" width="150">
  379. <template #default="scope">
  380. <input v-if="showEndTime(scope.row)"
  381. type='datetime-local'
  382. @click.stop
  383. @change="endTimeChange(scope.row.endTimeTemp, scope.row)"
  384. @contextmenu.stop.prevent="cancelStopTime(scope.row)"
  385. :style="endDateStyle(scope.row)"
  386. v-model="scope.row.endTimeTemp"/>
  387. <span v-else>{{ timeFomat(scope.row.endTime) }}</span>
  388. </template>
  389. </vxe-column>
  390. <vxe-column field="emergencyFlag" title="紧急" width="30">
  391. <template #default="{row}">
  392. {{ row.emergencyFlag === '1' ? '√' : '' }}
  393. </template>
  394. </vxe-column>
  395. <vxe-column field="ybSelfFlag" title="自费" width="30">
  396. <template #default="{row}">
  397. {{ row.ybSelfFlag === '1' ? '√' : '' }}
  398. </template>
  399. </vxe-column>
  400. <vxe-column field="physicianName" title="医生" width="65"/>
  401. <vxe-column field="selfBuyName" title="费用标志" width="60"/>
  402. <vxe-column field="execUnitName" title="执行科室" width="80"/>
  403. <vxe-column field="drugQuan" title="领量" width="30">
  404. <template #default="{row}">
  405. {{ nullToEmpty(row.drugQuan) + nullToEmpty(row.miniUnitName) }}
  406. </template>
  407. </vxe-column>
  408. <vxe-column field="groupNoName" title="药房" width="91"/>
  409. <vxe-column field="serial" title="序号" width="35"/>
  410. <vxe-column field="instruction" title="嘱托" width="180">
  411. <template #default="scope">
  412. <input v-if="scope.row.statusFlag === '1' || scope.row.statusFlag === '2' "
  413. :title="scope.row.instruction"
  414. v-model="scope.row.instruction"
  415. :maxlength="50"
  416. @keydown.enter="instructionEnter(scope.row)"
  417. />
  418. <span v-else>{{ scope.row.instruction }}</span>
  419. </template>
  420. </vxe-column>
  421. <vxe-column field="right" title="操作" width="95" fixed="right">
  422. <template #default="{row}">
  423. <el-button-group
  424. >
  425. <el-button text
  426. type="warning"
  427. @click.stop.prevent="emits('voidOrders', row)"
  428. >
  429. 作废
  430. </el-button>
  431. <el-button text
  432. type="danger"
  433. @click.stop.prevent="yzMitt.emit('deleteAnOrderByOrderNo', row, false)"
  434. >
  435. 删除
  436. </el-button>
  437. </el-button-group>
  438. </template>
  439. </vxe-column>
  440. </vxe-table>
  441. </template>
  442. <style lang="scss">
  443. @keyframes hzfirst {
  444. from {
  445. background-color: #95d475
  446. }
  447. to {
  448. background-color: white;
  449. }
  450. }
  451. .animation_hzfirst {
  452. td {
  453. animation: hzfirst 1s linear infinite
  454. }
  455. }
  456. .activation {
  457. background-color: #5376e7cc !important;
  458. color: white;
  459. }
  460. .parent_level {
  461. background-color: red;
  462. color: white;
  463. }
  464. .child_level {
  465. background-color: rgba(3, 255, 15);
  466. color: black;
  467. }
  468. </style>