reagentChange.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. <template>
  2. <div>
  3. <div v-if="show" class="reagentChange">
  4. <el-row type="flex">
  5. <el-col class="button">
  6. <div class="title">平行试验/留样再测</div>
  7. <div v-if="nodeId === 'Activity_0xkc1ji' || readonly" />
  8. <div v-else>
  9. <!-- <el-button type="primary" size="mini" icon="ibps-icon-edit" @click="openDialog">配置样品</el-button>
  10. <el-button type="success" size="mini" icon="ibps-icon-refresh" @click="generateData">重置</el-button>
  11. <el-button v-if="!disabled" type="danger" size="mini" icon="ibps-icon-calculator" @click="computedResult">计算结果</el-button>
  12. <el-button v-else type="danger" size="mini" icon="ibps-icon-edit" @click="disabled=false">编辑</el-button> -->
  13. <el-button type="primary" size="mini" icon="ibps-icon-download" @click="handleDownload">模版下载</el-button>
  14. <el-button type="primary" size="mini" icon="ibps-icon-import" @click="handleImport">导入</el-button>
  15. </div>
  16. </el-col>
  17. </el-row>
  18. <el-row type="flex">
  19. <el-col>
  20. <el-table ref="reagent" :data="reagentDataFilter" :span-method="spanMethod">
  21. <el-table-column
  22. :label="formData.yiYuan === '深圳肿瘤' ? '检查项目' : '检验项目'"
  23. prop="jyxm"
  24. />
  25. <el-table-column label="浓度" prop="nd">
  26. <template slot-scope="{row}">
  27. <el-input v-if="!disabled" v-model="row.nd" size="mini" placeholder="请输入" />
  28. <span v-else>{{ row.nd|| '/' }}</span>
  29. </template>
  30. </el-table-column>
  31. <el-table-column :label="formData.yiYuan === '深圳肿瘤' ? '病理号' : '样品编号'" prop="ypbh" />
  32. <el-table-column label="旧试剂测得结果" prop="jsjcdjg">
  33. <template slot-scope="{row}">
  34. <el-input v-if="!disabled" v-model="row.jsjcdjg" :min="0" size="mini" placeholder="请输入" type="number" />
  35. <span v-else>{{ row.jsjcdjg|| '/' }}</span>
  36. </template>
  37. </el-table-column>
  38. <el-table-column label="新试剂测得结果" prop="xsjcdjg">
  39. <template slot-scope="{row}">
  40. <el-input v-if="!disabled" v-model="row.xsjcdjg" :min="0" size="mini" placeholder="请输入" type="number" />
  41. <span v-else>{{ row.xsjcdjg|| '/' }}</span>
  42. </template>
  43. </el-table-column>
  44. <el-table-column label="实际差值" prop="sjcz" />
  45. <el-table-column label="实际偏倚" prop="pq" />
  46. <el-table-column :label="isHz?'允许差值':'限定范围'" prop="xdfw" />
  47. <el-table-column label="允许偏倚" prop="yxpq" />
  48. <el-table-column label="是否相符" prop="sfxf">
  49. <template slot-scope="{row}">
  50. <el-radio-group v-model="row.sfxf" disabled>
  51. <el-radio label="是">是</el-radio>
  52. <el-radio label="否">否</el-radio>
  53. </el-radio-group>
  54. </template>
  55. </el-table-column>
  56. <el-table-column label="符合率" prop="fhl" />
  57. <el-table-column label="符合率可接受标准" prop="xmfhl" />
  58. <el-table-column label="结论" prop="jl" />
  59. </el-table>
  60. <el-pagination
  61. layout="total,sizes,prev, pager, next,jumper"
  62. :current-page="requestPage.pageNo"
  63. :page-size="requestPage.limit"
  64. :page-sizes="[10,15,20,30,50,100]"
  65. :total="reagentData.length"
  66. @size-change="handleSizeChange"
  67. @current-change="handleCurrentChange"
  68. />
  69. </el-col>
  70. </el-row>
  71. </div>
  72. <el-dialog
  73. class="ragent-dialog"
  74. title="配置样品"
  75. :visible.sync="centerDialogVisible"
  76. width="30%"
  77. append-to-body
  78. center
  79. >
  80. <div v-for="(item, index) in dialogData" :key="index" class="ragent-dialog-content">
  81. <span>样品编号{{ index+1 }}</span>
  82. <div style="display: flex;align-items: center;">
  83. <span style="color: red;margin-right: 3px;">*</span>
  84. <el-input v-model="item.number" required :min="0" style="flex:1;" />
  85. </div>
  86. <div>
  87. <el-button type="text" :style="{visibility: index === dialogData.length-1?'visible':'hidden'}" @click="addRow">添加</el-button>
  88. <el-button type="text" :style="{visibility: index === dialogData.length-1?'visible':'hidden'}" style="color:red;" @click="deleteRow(index)">删除</el-button>
  89. </div>
  90. </div>
  91. <span slot="footer" class="dialog-footer">
  92. <el-button @click="centerDialogVisible = false">取 消</el-button>
  93. <el-button type="primary" @click="dialogDataConfirm">确 定</el-button>
  94. </span>
  95. </el-dialog>
  96. <import-table
  97. :visible="importTableDialogVisible"
  98. title="导入"
  99. @close="(visible) => (importTableDialogVisible = visible)"
  100. @action-event="handleImportTableActionEvent"
  101. />
  102. </div>
  103. </template>
  104. <script>
  105. import importTable from '@/business/platform/form/formrender/dynamic-form/components/import-table'
  106. import IbpsImport from '@/plugins/import'
  107. import { downloadFile } from '@/business/platform/file/utils'
  108. export default {
  109. components: {
  110. importTable
  111. },
  112. props: {
  113. formData: {
  114. type: Object,
  115. default: () => {}
  116. },
  117. readonly: {
  118. type: Boolean,
  119. default: false
  120. },
  121. params: {
  122. type: Object,
  123. default: () => {}
  124. }
  125. },
  126. data () {
  127. return {
  128. reagentData: [],
  129. dialogData: [
  130. { number: '' },
  131. { number: '' },
  132. { number: '' },
  133. { number: '' },
  134. { number: '' }
  135. ],
  136. copyDialogData: [],
  137. centerDialogVisible: false,
  138. validateFlag: false,
  139. disabled: false,
  140. ypData: [],
  141. ypFlag: false,
  142. nodeId: '',
  143. spanLength: 0,
  144. show: true,
  145. requestPage: {
  146. limit: 20,
  147. pageNo: 1
  148. },
  149. importTableDialogVisible: false,
  150. isHz: false
  151. }
  152. },
  153. computed: {
  154. reagentDataFilter () {
  155. return this.reagentData.slice((this.requestPage.pageNo - 1) * (this.requestPage.limit), (this.requestPage.pageNo - 1) * (this.requestPage.limit) + this.requestPage.limit)
  156. }
  157. },
  158. watch: {
  159. // disabled: {
  160. // handler (val) {
  161. // this.$emit('change-data', 'zuJianShuJu', JSON.stringify([this.reagentData, this.copyDialogData.length, val]))
  162. // },
  163. // immediate: true
  164. // },
  165. 'formData.sjghyzjlbxmcszb': {
  166. // 在表单中的任何操作都会触发子表的监听
  167. handler (val) {
  168. this.ypData = []
  169. this.ypFlag = false
  170. val.forEach(item => {
  171. // 判断“平行实验/留样再测”表是否全填
  172. if (!item.jianCeXiangMu || !item.xiangMuFuHeLv || !item.xianDingFanWei) {
  173. this.ypFlag = true
  174. }
  175. this.ypData.push({ ...item })
  176. })
  177. },
  178. deep: true,
  179. immediate: true
  180. },
  181. 'formData.sjghyzjlbbbzb': {
  182. handler (val) {
  183. if (this.formData.zuJianShuJu) {
  184. const data = JSON.parse(this.formData.zuJianShuJu)
  185. this.spanLength = data[1] || 0
  186. }
  187. if (val?.length && this.reagentData?.length <= 0) {
  188. const arry = []
  189. val.forEach(item => {
  190. arry.push({ jyxm: item.jianCeXiangMu, nd: item.nongDu, ypbh: item.biaoBenHao, jsjcdjg: item.jiuJieGuo, xsjcdjg: item.xinJieGuo, pq: item.jieGuo, fhl: item.biaoZhun, sfxf: item.xiangFu, jl: item.jieLun, xdfw: item.zuiXiaoFanWei, xmfhl: item.xiangMuFuHeLv, sjcz: item.shiJiChaZhi, yxpq: item.yunXuPianYi })
  191. })
  192. this.reagentData = arry
  193. }
  194. },
  195. deep: true,
  196. immediate: true
  197. },
  198. 'formData.fangAn': {
  199. handler (val) {
  200. this.showAndHide(val)
  201. },
  202. immediate: true
  203. }
  204. },
  205. mounted () {
  206. const { first = '' } = this.$store.getters.level
  207. const { deptList = [] } = this.$store.getters || {}
  208. const t1 = deptList.find(i => i.positionId === first) || {}
  209. this.isHz = t1.positionName === '惠州市第三人民医院'
  210. console.log(t1.positionName)
  211. this.nodeId = this.params ? this.params.nodeId : ''
  212. this.spanLength = this.params ? this.params.spanLength : 0
  213. this.disabled = this.readonly || this.nodeId === 'Activity_0xkc1ji'
  214. this.showAndHide(this.formData.fangAn)
  215. },
  216. methods: {
  217. handleImport () {
  218. this.importTableDialogVisible = true
  219. },
  220. handleDownload () {
  221. downloadFile({ id: 'download_sjghdl', fileName: '试剂更换验证定量模板', ext: 'xlsx' })
  222. },
  223. getColumns () {
  224. return [{
  225. field_name: 'sfxf',
  226. label: '是否相符',
  227. name: 'sfxf'
  228. },
  229. {
  230. field_name: 'jyxm',
  231. label: this.formData.yiYuan === '深圳肿瘤' ? '检查项目' : '检验项目',
  232. name: 'jyxm'
  233. },
  234. {
  235. field_name: 'nd',
  236. label: '浓度',
  237. name: 'nd'
  238. },
  239. {
  240. field_name: 'ypbh',
  241. label: this.formData.yiYuan === '深圳肿瘤' ? '病理号' : '样品编号',
  242. name: 'ypbh'
  243. },
  244. {
  245. field_name: 'jsjcdjg',
  246. label: '旧试剂测得结果',
  247. name: 'jsjcdjg'
  248. },
  249. {
  250. field_name: 'xsjcdjg',
  251. label: '新试剂测得结果',
  252. name: 'xsjcdjg'
  253. },
  254. {
  255. field_name: 'sjcz',
  256. label: '实际差值',
  257. name: 'sjcz'
  258. },
  259. {
  260. field_name: 'pq',
  261. label: '实际偏倚',
  262. name: 'pq'
  263. },
  264. {
  265. field_name: 'xdfw',
  266. label: this.isHz ? '允许差值' : '限定范围',
  267. name: 'xdfw'
  268. },
  269. {
  270. field_name: 'yxpq',
  271. label: '允许偏倚',
  272. name: 'yxpq'
  273. },
  274. {
  275. field_name: 'fhl',
  276. label: '符合率',
  277. name: 'fhl'
  278. }, {
  279. field_name: 'jl',
  280. label: '结论',
  281. name: 'jl'
  282. },
  283. {
  284. field_name: 'xmfhl',
  285. label: '符合率可接受标准',
  286. name: 'xmfhl'
  287. }]
  288. },
  289. getKeys (data) {
  290. return Array.isArray(data) ? data.reduce((acc, item) => ({ ...acc, [item.label]: item.name }), {}) : {}
  291. },
  292. handleImportTableActionEvent (file, options) {
  293. debugger
  294. IbpsImport.xlsx(file, options).then(({ header, results }) => {
  295. debugger
  296. const list = []
  297. const keys = this.getKeys(this.getColumns())
  298. results.forEach(item => {
  299. const obj = {}
  300. Object.keys(item).forEach(key => {
  301. if (keys[key]) {
  302. obj[keys[key]] = item[key]
  303. }
  304. })
  305. list.push(obj)
  306. })
  307. /*
  308. const filteredArray = list.map(item => {
  309. if (item.jyxm && item.fhl && item.jl) {
  310. return item
  311. }
  312. return null
  313. }).filter(item => item !== null)
  314. filteredArray.forEach(item => {
  315. list.forEach(el => {
  316. if (el.jyxm === item.jyxm) {
  317. el.fhl = item.fhl
  318. el.jl = item.jl
  319. el.xmfhl = item.xmfhl
  320. }
  321. if (el.sjcz) {
  322. el.sjcz = (el.sjcz + '').replace('-', '')
  323. }
  324. if (el.pq) {
  325. el.pq = (el.pq + '').replace('-', '')
  326. }
  327. })
  328. })*/
  329. this.reagentData = list
  330. this.disabled = true
  331. setTimeout(() => {
  332. this.$nextTick(() => {
  333. this.$refs.reagent && this.$refs.reagent.$forceUpdate()
  334. })
  335. })
  336. this.importTableDialogVisible = false
  337. this.$emit('change-data', 'zuJianShuJu', JSON.stringify([this.reagentData, this.copyDialogData.length, this.disabled]))
  338. })
  339. },
  340. // 当前页码改变
  341. handleCurrentChange (val) {
  342. this.requestPage.pageNo = val
  343. },
  344. // 页码选择器改变
  345. handleSizeChange (val) {
  346. this.requestPage.limit = val
  347. this.requestPage.pageNo = 1
  348. },
  349. showAndHide (data) {
  350. if (data.includes('平行试验') || data.includes('留样再测') || data.includes('比对方案')) {
  351. this.show = true
  352. } else {
  353. this.show = false
  354. }
  355. },
  356. // 配置样品
  357. openDialog () {
  358. this.centerDialogVisible = true
  359. if (this.copyDialogData.length > 0) {
  360. this.dialogData = JSON.parse(JSON.stringify(this.copyDialogData))
  361. } else {
  362. this.dialogData = [
  363. { number: '' },
  364. { number: '' },
  365. { number: '' },
  366. { number: '' },
  367. { number: '' }
  368. ]
  369. }
  370. },
  371. // 生成数据
  372. generateData () {
  373. if (this.copyDialogData.length > 0 && this.ypData.length > 0 && !this.ypFlag) {
  374. if (this.reagentData.length > 0) {
  375. this.$confirm('将重置表格数据,是否确认操作?', '提示', {
  376. confirmButtonText: '确定',
  377. cancelButtonText: '取消',
  378. type: 'warning'
  379. }).then(() => {
  380. // 将配置样品数据填入reagentData
  381. this.initData()
  382. })
  383. } else {
  384. // 将配置样品数据填入reagentData
  385. this.initData()
  386. }
  387. } else {
  388. this.$message.warning('请先配置样品数据')
  389. }
  390. },
  391. // 计算结果
  392. computedResult () {
  393. // computedFlag:true表示里面有空数据或填写不规范数据
  394. let computedFlag = false
  395. this.reagentData.forEach(item => {
  396. if (!item.jsjcdjg || item.jsjcdjg <= 0 || !item.xsjcdjg || item.xsjcdjg <= 0) {
  397. computedFlag = true
  398. }
  399. })
  400. if (!computedFlag && this.reagentData.length > 0) {
  401. this.reagentData.forEach(item => {
  402. item.pq = this.deleteAccuracy(Math.abs(item.xsjcdjg - item.jsjcdjg) / item.jsjcdjg)
  403. item.sfxf = Number(item.pq.replace('%', '')) <= Number(item.xdfw.replace('%', '')) ? '是' : '否'
  404. })
  405. this.ypData.forEach(c => {
  406. const count = this.reagentData.filter(item => item.jyxm === c.jianCeXiangMu && item.sfxf === '是').length
  407. this.reagentData.forEach(item => {
  408. if (item.jyxm === c.jianCeXiangMu) {
  409. item.fhl = this.deleteAccuracy(count / this.copyDialogData.length)
  410. item.jl = Number(item.fhl.replace('%', '')) >= Number(c.xiangMuFuHeLv.replace('%', '')) ? '合格' : '不合格'
  411. }
  412. })
  413. })
  414. this.disabled = true
  415. this.$emit('change-data', 'zuJianShuJu', JSON.stringify([this.reagentData, this.copyDialogData.length, this.disabled]))
  416. } else {
  417. this.$message.warning('试剂测得结果必须大于0且不能为空!')
  418. }
  419. },
  420. initData () {
  421. this.disabled = false
  422. this.reagentData = []
  423. this.ypData.forEach(item => {
  424. this.copyDialogData.forEach(el => {
  425. this.reagentData.push({ jyxm: item.jianCeXiangMu, nd: '', ypbh: el.number, jsjcdjg: '', xsjcdjg: '', pq: '', xdfw: item.xianDingFanWei + '%', sfxf: '', fhl: '', jl: '', xmfhl: item.xiangMuFuHeLv + '%', sjcz: '', yxpq: '' })
  426. })
  427. })
  428. this.$refs.reagent.doLayout()
  429. },
  430. spanMethod ({ row, column, rowIndex, columnIndex }) {
  431. // const rowspan = this.copyDialogData.length || this.spanLength
  432. if (columnIndex === 0 || columnIndex === 10 || columnIndex === 11 || columnIndex === 12) {
  433. const currentValue = row[column.property]
  434. const preRow = this.reagentData[rowIndex - 1]
  435. // 上一行这一列的数据
  436. const preValue = preRow ? preRow[column.property] : null
  437. // 如果当前值和上一行的值相同,则将当前单元格隐藏
  438. // 给第0,10,11,12列对数值相同且是同一个'jyxm'进行表格合并
  439. if (currentValue === preValue && row['jyxm'] === preRow['jyxm']) {
  440. return { rowspan: 0, colspan: 0 }
  441. } else {
  442. let rowspan = 1
  443. // 计算应该合并的行数
  444. for (let i = rowIndex + 1; i < this.reagentData.length; i++) {
  445. const nextRow = this.reagentData[i]
  446. const nextValue = nextRow[column.property]
  447. if (nextValue === currentValue && nextRow['jyxm'] === row['jyxm']) {
  448. rowspan++
  449. } else {
  450. break
  451. }
  452. }
  453. return { rowspan, colspan: 1 }
  454. }
  455. // if (rowIndex % rowspan === 0) {
  456. // return {
  457. // rowspan,
  458. // colspan: 1
  459. // }
  460. // } else {
  461. // return {
  462. // rowspan: 0,
  463. // colspan: 0
  464. // }
  465. // }
  466. }
  467. },
  468. addRow () {
  469. this.dialogData.push({ number: '' })
  470. },
  471. deleteRow (index) {
  472. if (this.dialogData.length === 1) {
  473. this.$message.warning('删除失败,样品不能为空!')
  474. } else {
  475. this.dialogData.splice(index, 1)
  476. }
  477. },
  478. dialogDataConfirm () {
  479. // validateFlag:true 表单校验未通过
  480. this.validateFlag = false
  481. this.dialogData.forEach(item => {
  482. if (!item.number) {
  483. this.validateFlag = true
  484. }
  485. })
  486. if (this.validateFlag) {
  487. this.$message.warning('样品编号未填写完!')
  488. } else {
  489. this.centerDialogVisible = false
  490. this.copyDialogData = JSON.parse(JSON.stringify(this.dialogData))
  491. // 样品配置完自定生成表格数据,先判断样品配置表数据是否全填
  492. if (this.ypData.length > 0 && !this.ypFlag) {
  493. this.initData()
  494. } else {
  495. this.$message.warning('请完成样品参数配置')
  496. }
  497. }
  498. },
  499. // 去除小数*100精度方法
  500. deleteAccuracy (num) {
  501. // 是否带小数点
  502. if ((num.toString()).includes('.')) {
  503. // 保留小数点后面3位
  504. const numArry = num.toFixed(3).toString().split('.')
  505. // 整数位是否大于0
  506. if (numArry[0] > 0) {
  507. return Number(numArry[0] + numArry[1].substring(0, 2) + '.' + numArry[1].substring(2, 3)) + '%'
  508. } else {
  509. // 小数位第一位是否大于0
  510. if (numArry[1][0] > 0) {
  511. return Number(numArry[1].substring(0, 2) + '.' + numArry[1].substring(2, 3)) + '%'
  512. } else {
  513. // 小数位第二位是否大于0
  514. if (numArry[1][1] > 0) {
  515. return Number(numArry[1].substring(1, 2) + '.' + numArry[1].substring(2, 3)) + '%'
  516. } else {
  517. return Number(0 + '.' + numArry[1].substring(2, 3)) + '%'
  518. }
  519. }
  520. }
  521. } else {
  522. return num * 100 + '%'
  523. }
  524. }
  525. }
  526. }
  527. </script>
  528. <style lang="scss" scoped>
  529. .reagentChange{
  530. margin-bottom: 20px;
  531. .button{
  532. display: flex;
  533. justify-content: space-between;
  534. padding: 0px 0px 0px 15px;
  535. background: #f0ffff;
  536. .title {
  537. color: #999;
  538. font-size: 12px;
  539. font-weight: bold;
  540. margin-bottom: 0;
  541. }
  542. .el-button {
  543. margin: 0;
  544. }
  545. }
  546. }
  547. .ragent-dialog ::v-deep .el-dialog__body {
  548. max-height: 300px;
  549. }
  550. .el-dialog__body .ragent-dialog-content {
  551. display: flex;
  552. align-items: center;
  553. justify-content: space-around;
  554. padding: 10px;
  555. }
  556. </style>