A8000
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

469 lines
12 KiB

8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
  1. <template>
  2. <div id="configuration-container" v-loading="loading">
  3. <!-- 渲染试管架列表 -->
  4. <div class="tube-rack-list">
  5. <div
  6. v-for="(tubeRack, index) in testTubeStore.tubeRacks"
  7. :key="tubeRack.uuid"
  8. class="tube-rack-container"
  9. >
  10. <TestTubeRackComponent
  11. :tubeRack="tubeRack"
  12. :index="index"
  13. :projects="consumablesStore.projectsAvailable"
  14. :bloodTypes="settingTubeStore.bloodTypes"
  15. @delete:rack="deleteTubeRack"
  16. @active:rack="handleActivateChange"
  17. @patient:edit="handleChangeUser"
  18. @clickTubeItem="updateTubeSettings"
  19. />
  20. </div>
  21. <!-- 添加试管架按钮 -->
  22. <div class="add-tube" @click="addTubeRack">
  23. <div class="icon">
  24. <img src="@/assets/add.svg" alt="Add Icon" />
  25. </div>
  26. <div class="text">添加试管架</div>
  27. </div>
  28. </div>
  29. <div class="setting-panel">
  30. <section class="project-area">
  31. <h2 class="title">项目选择</h2>
  32. <div class="project-list">
  33. <div
  34. v-for="proj in consumablesStore.projectsAvailable"
  35. :key="proj.projName"
  36. >
  37. <div
  38. class="project-item"
  39. :class="{ active: isProjElemActive(proj) }"
  40. :style="styleOfProjElem(proj)"
  41. @click="clickProjectItem(proj)"
  42. >
  43. <span class="proj-name">{{ proj.projName }}</span>
  44. <span class="proj-num">{{
  45. `${projIdCntMap[proj.projId || 1] || 0}/${proj.num}`
  46. }}</span>
  47. </div>
  48. </div>
  49. </div>
  50. </section>
  51. <section class="blood-type-area">
  52. <h2 class="title">血液类型</h2>
  53. <div class="blood-list">
  54. <div v-for="type in settingTubeStore.bloodTypes" :key="type.key">
  55. <div
  56. class="blood-item"
  57. :class="{ active: selectedBloodTypeKey === type.key }"
  58. @click="clickBloodTypeItem(type)"
  59. >
  60. {{ type.name }}
  61. </div>
  62. </div>
  63. </div>
  64. </section>
  65. </div>
  66. <ConfirmModal v-if="confirmVisible" :confirmInfo="confirmInfo"/>
  67. </div>
  68. </template>
  69. <script setup lang="ts">
  70. import { ref, computed } from 'vue'
  71. import * as R from 'ramda'
  72. import { useRouter } from 'vue-router'
  73. import TestTubeRackComponent from '../components/TestTube/TestTubeRack.vue'
  74. import {
  75. addTestTube,
  76. deleteTube,
  77. updateTubeConfig,
  78. updateTubeActivationStatus,
  79. } from '../../../services/Index/testTube'
  80. import {
  81. useConsumablesStore,
  82. useTestTubeStore,
  83. useSettingTestTubeStore,
  84. } from '../../../store'
  85. import { BloodType, ReactionPlateGroup, TestTubeRack } from '@/websocket/socket'
  86. import { eMessage } from '../utils'
  87. const router = useRouter()
  88. const settingTubeStore = useSettingTestTubeStore()
  89. const testTubeStore = useTestTubeStore()
  90. const consumablesStore = useConsumablesStore()
  91. const loading = ref(false) // 控制加载状态
  92. const selectedProjIds = ref<number[]>([])
  93. const selectedBloodTypeKey = ref<BloodType | undefined>('WHOLE_BLOOD')
  94. const isProjElemActive = (proj: ReactionPlateGroup) => {
  95. return selectedProjIds.value.includes(proj.projId!)
  96. }
  97. const styleOfProjElem = (proj: ReactionPlateGroup) => {
  98. const active = isProjElemActive(proj)
  99. if (active) {
  100. return {
  101. border: 'solid 1px transparent',
  102. backgroundColor: consumablesStore.projIdColorMap[proj.projId!],
  103. color: '#FFF',
  104. }
  105. } else {
  106. return {
  107. border: `solid 1px ${consumablesStore.projIdColorMap[proj.projId!]}`,
  108. backgroundColor: '#FFF',
  109. color: consumablesStore.projIdColorMap[proj.projId!],
  110. }
  111. }
  112. }
  113. const clickProjectItem = (proj: ReactionPlateGroup) => {
  114. if (selectedProjIds.value.includes(proj.projId!)) {
  115. selectedProjIds.value = selectedProjIds.value.filter(
  116. (pId) => pId !== proj.projId,
  117. )
  118. } else {
  119. selectedProjIds.value = [...selectedProjIds.value, proj.projId!].sort()
  120. }
  121. }
  122. const clickBloodTypeItem = (type) => {
  123. if (selectedBloodTypeKey.value === type.key) {
  124. selectedBloodTypeKey.value = undefined
  125. } else {
  126. selectedBloodTypeKey.value = type.key
  127. }
  128. }
  129. const projIdCntMap = computed(() => {
  130. let projMap = R.reduce(
  131. (acc, curr) => {
  132. const projIdCntRack = R.reduce(
  133. (ac, cur) => {
  134. cur.projId.forEach((pId) => {
  135. ac[pId] = (ac[pId] || 0) + 1
  136. })
  137. return ac
  138. },
  139. {},
  140. curr.tubeSettings,
  141. )
  142. Object.keys(projIdCntRack).forEach((k) => {
  143. acc[k] = (acc[k] || 0) + projIdCntRack[k]
  144. })
  145. return acc
  146. },
  147. {},
  148. testTubeStore.tubeRacks,
  149. )
  150. return projMap;
  151. })
  152. //编辑患者信息
  153. const handleChangeUser = async (index: number) => {
  154. const rack = testTubeStore.tubeRacks[index]
  155. if (rack.state !== 'INACTIVE') {
  156. eMessage.error('试管架处理激活状态,不可修改')
  157. return
  158. }
  159. testTubeStore.setTubeRack(rack)
  160. router.push({
  161. path: '/index/change-user',
  162. })
  163. }
  164. const existValidProject = (tubeRack: TestTubeRack) => {
  165. return tubeRack.tubeSettings.some(
  166. (s) => (s.projId || []).length > 0 && !!s.bloodType,
  167. )
  168. }
  169. let confirmDeleteRackUuid: string | undefined = undefined
  170. const confirmDeleteTube = async () => {
  171. const res = await deleteTube(confirmDeleteRackUuid!)
  172. if (res.success) {
  173. eMessage.success('删除成功')
  174. } else {
  175. eMessage.error('删除失败')
  176. }
  177. }
  178. //删除试管架
  179. const confirmVisible = ref(false)
  180. const confirmInfo = ref({})
  181. const deleteTubeRack = async (idx: number) => {
  182. const rack = testTubeStore.tubeRacks[idx]
  183. if (rack.state !== 'INACTIVE') {
  184. eMessage.error('试管架处理激活状态,不可删除')
  185. return
  186. }
  187. confirmDeleteRackUuid = rack.uuid
  188. if (existValidProject(rack)) {
  189. confirmVisible.value = true;
  190. confirmInfo.value = {
  191. title: '确认删除',
  192. message: '存在配置了检测项目的试管,确定删除?',
  193. cancelText: '取消',
  194. confirmText: '确认',
  195. onCancel:()=>{
  196. confirmVisible.value = false
  197. },
  198. onConfirm:async()=>{
  199. await confirmDeleteTube()
  200. confirmVisible.value = false
  201. }
  202. }
  203. return
  204. }
  205. confirmVisible.value = true;
  206. confirmInfo.value = {
  207. title: '确认删除',
  208. message: '请确认是否删除?',
  209. cancelText: '取消',
  210. confirmText: '确认',
  211. onCancel:()=>{
  212. confirmVisible.value = false
  213. },
  214. onConfirm:async()=>{
  215. await confirmDeleteTube()
  216. confirmVisible.value = false
  217. }
  218. }
  219. }
  220. // 添加新试管架
  221. const addTubeRack = async () => {
  222. loading.value = true
  223. const response = await addTestTube()
  224. if (response && response.success) {
  225. // do nothing
  226. } else {
  227. eMessage.error('试管架添加失败')
  228. }
  229. loading.value = false
  230. }
  231. const projIdsOfTube = (tube) => {
  232. return tube ? (tube.projId || tube.projIds || []) : []
  233. }
  234. // 处理试管架激活状态变化
  235. const handleActivateChange = async (index: number) => {
  236. const rack = testTubeStore.tubeRacks[index]
  237. console.log('---', rack)
  238. if(rack.tubeSettings.every((tube) => projIdsOfTube(tube).length === 0)) {
  239. eMessage.error('请为试管配置检测项目')
  240. return
  241. }
  242. if (rack.state === 'LOCKED') {
  243. eMessage.error('试管已锁定,不能修改')
  244. return
  245. }
  246. let destState = rack.state === 'INACTIVE' ? 'ACTIVE' : 'INACTIVE'
  247. // if (destState === 'ACTIVE' && !existValidProject(rack)) {
  248. // eMessage.error('请为试管配置检测项目')
  249. // return
  250. // }
  251. const res = await updateTubeActivationStatus({
  252. uuid: rack.uuid,
  253. active: destState === 'ACTIVE',
  254. })
  255. if (res.success) {
  256. // do nothing
  257. } else {
  258. eMessage.error('更改激活状态失败')
  259. }
  260. }
  261. const updateTubeSettings = async (rackIdx: number, tubeIdx: number) => {
  262. const rack = testTubeStore.tubeRacks[rackIdx]
  263. if (!consumablesStore.projectsAvailable.length) {
  264. eMessage.error('请先添加项目')
  265. return
  266. }
  267. if (!selectedProjIds.value.length && (!rack.tubeSettings[tubeIdx].projId || !rack.tubeSettings[tubeIdx].projId.length)) {
  268. eMessage.error('请选择项目')
  269. return
  270. }
  271. if (!selectedBloodTypeKey.value) {
  272. eMessage.error('请选择血液类型')
  273. return
  274. }
  275. if (rack.state !== 'INACTIVE') {
  276. eMessage.error('试管架已激活,不能修改')
  277. return
  278. }
  279. if (tubeIdx < rack.tubeSettings.length) {
  280. const setting = rack.tubeSettings[tubeIdx]
  281. const updSetting = {
  282. ...setting,
  283. projId: selectedProjIds.value,
  284. bloodType: selectedBloodTypeKey.value,
  285. }
  286. try {
  287. const response = await updateTubeConfig({
  288. uuid: rack.uuid,
  289. setting: [updSetting],
  290. })
  291. if (response.success) {
  292. // do nothing
  293. } else {
  294. eMessage.error('设置更新失败')
  295. }
  296. } catch (error) {
  297. console.error('更新试管设置失败:', error)
  298. eMessage.error('设置更新失败')
  299. }
  300. }
  301. }
  302. </script>
  303. <style scoped lang="less">
  304. #configuration-container {
  305. > * {
  306. box-sizing: border-box;
  307. }
  308. @active-color: rgb(82, 140, 254);
  309. @setting-panel-height: 13.5625rem;
  310. box-sizing: border-box;
  311. position: relative;
  312. height: calc(100vh - @setting-panel-height);
  313. .el-message {
  314. width: 200px;
  315. height: 200px;
  316. }
  317. /* 主容器定高和滚动条样式 */
  318. .tube-rack-list {
  319. height: calc(100% - @setting-panel-height);
  320. /* 根据需要设置主容器的最大高度 */
  321. overflow-y: auto;
  322. overflow-x: hidden;
  323. width: 100%;
  324. /* 自定义滚动条样式 */
  325. &::-webkit-scrollbar {
  326. width: 8px;
  327. }
  328. &::-webkit-scrollbar-track {
  329. background: #f1f1f1;
  330. }
  331. &::-webkit-scrollbar-thumb {
  332. background: #888;
  333. border-radius: 4px;
  334. }
  335. &::-webkit-scrollbar-thumb:hover {
  336. background: #555;
  337. }
  338. }
  339. .setting-panel {
  340. @line-color: rgb(200, 200, 200);
  341. position: absolute;
  342. bottom: 0;
  343. width: 100%;
  344. height: @setting-panel-height;
  345. padding: 0 40px;
  346. border-top: solid 1px @line-color;
  347. border-bottom: solid 1px @line-color;
  348. .title {
  349. font-size: 24px;
  350. font-weight: bold;
  351. margin-right: 40px;
  352. }
  353. .project-area {
  354. display: flex;
  355. align-items: center;
  356. .project-list {
  357. display: flex;
  358. .project-item {
  359. border-radius: 8px;
  360. padding: 4px 10px;
  361. margin-right: 8px;
  362. display: flex;
  363. flex-direction: column;
  364. align-items: center;
  365. min-width: 50px;
  366. .proj-name {
  367. font-weight: 600;
  368. font-size: 1.2rem;
  369. }
  370. }
  371. }
  372. }
  373. .blood-type-area {
  374. display: flex;
  375. align-items: center;
  376. .blood-list {
  377. display: flex;
  378. .blood-item {
  379. border: solid 1px @active-color;
  380. border-radius: 4px;
  381. padding: 10px;
  382. margin-right: 8px;
  383. color: @active-color;
  384. font-weight: 600;
  385. font-size: 1.2rem;
  386. &.active {
  387. color: #fff;
  388. background-color: @active-color;
  389. }
  390. }
  391. }
  392. }
  393. }
  394. .add-tube {
  395. width: 100%;
  396. height: 100px;
  397. background-color: #f6f6f6;
  398. display: flex;
  399. align-items: center;
  400. justify-content: center;
  401. .icon {
  402. img {
  403. width: 60px;
  404. }
  405. margin-right: 10px;
  406. display: flex;
  407. align-items: center;
  408. justify-content: center;
  409. }
  410. .text {
  411. font-size: 32px;
  412. color: #73bc54;
  413. font-weight: bold;
  414. }
  415. }
  416. /* 加载状态样式 */
  417. .loading-overlay {
  418. position: fixed;
  419. top: 0;
  420. left: 0;
  421. width: 100%;
  422. height: 100vh;
  423. display: flex;
  424. align-items: center;
  425. justify-content: center;
  426. background-color: rgba(255, 255, 255, 0.8);
  427. font-size: 24px;
  428. color: #333;
  429. z-index: 1000;
  430. }
  431. }
  432. </style>