reagentChange.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  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. if (val.length && this.reagentData.length <= 0) {
  187. const arry = []
  188. val.forEach(item => {
  189. 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 })
  190. })
  191. setTimeout(() => {
  192. this.reagentData = arry
  193. this.$nextTick(() => {
  194. this.$refs.reagent && this.$refs.reagent.$forceUpdate()
  195. })
  196. })
  197. }
  198. }
  199. },
  200. deep: true
  201. },
  202. 'formData.fangAn': {
  203. handler (val) {
  204. this.showAndHide(val)
  205. },
  206. immediate: true
  207. }
  208. },
  209. mounted () {
  210. const { first = '' } = this.$store.getters.level
  211. const { deptList = [] } = this.$store.getters || {}
  212. const t1 = deptList.find(i => i.positionId === first) || {}
  213. this.isHz = t1.positionName === '惠州市第三人民医院'
  214. console.log(t1.positionName)
  215. this.nodeId = this.params ? this.params.nodeId : ''
  216. this.spanLength = this.params ? this.params.spanLength : 0
  217. this.disabled = this.readonly || this.nodeId === 'Activity_0xkc1ji'
  218. this.showAndHide(this.formData.fangAn)
  219. },
  220. methods: {
  221. handleImport () {
  222. this.importTableDialogVisible = true
  223. },
  224. handleDownload () {
  225. downloadFile({ id: 'download_sjghdl', fileName: '试剂更换验证定量模板', ext: 'xlsx' })
  226. },
  227. getColumns () {
  228. return [{
  229. field_name: 'sfxf',
  230. label: '是否相符',
  231. name: 'sfxf'
  232. },
  233. {
  234. field_name: 'jyxm',
  235. label: this.formData.yiYuan === '深圳肿瘤' ? '检查项目' : '检验项目',
  236. name: 'jyxm'
  237. },
  238. {
  239. field_name: 'nd',
  240. label: '浓度',
  241. name: 'nd'
  242. },
  243. {
  244. field_name: 'ypbh',
  245. label: this.formData.yiYuan === '深圳肿瘤' ? '病理号' : '样品编号',
  246. name: 'ypbh'
  247. },
  248. {
  249. field_name: 'jsjcdjg',
  250. label: '旧试剂测得结果',
  251. name: 'jsjcdjg'
  252. },
  253. {
  254. field_name: 'xsjcdjg',
  255. label: '新试剂测得结果',
  256. name: 'xsjcdjg'
  257. },
  258. {
  259. field_name: 'sjcz',
  260. label: '实际差值',
  261. name: 'sjcz'
  262. },
  263. {
  264. field_name: 'pq',
  265. label: '实际偏倚',
  266. name: 'pq'
  267. },
  268. {
  269. field_name: 'xdfw',
  270. label: this.isHz ? '允许差值' : '限定范围',
  271. name: 'xdfw'
  272. },
  273. {
  274. field_name: 'yxpq',
  275. label: '允许偏倚',
  276. name: 'yxpq'
  277. },
  278. {
  279. field_name: 'fhl',
  280. label: '符合率',
  281. name: 'fhl'
  282. }, {
  283. field_name: 'jl',
  284. label: '结论',
  285. name: 'jl'
  286. },
  287. {
  288. field_name: 'xmfhl',
  289. label: '符合率可接受标准',
  290. name: 'xmfhl'
  291. }]
  292. },
  293. getKeys (data) {
  294. return Array.isArray(data) ? data.reduce((acc, item) => ({ ...acc, [item.label]: item.name }), {}) : {}
  295. },
  296. handleImportTableActionEvent (file, options) {
  297. IbpsImport.xlsx(file, options).then(({ header, results }) => {
  298. const list = []
  299. const keys = this.getKeys(this.getColumns())
  300. results.forEach(item => {
  301. const obj = {}
  302. Object.keys(item).forEach(key => {
  303. if (keys[key]) {
  304. obj[keys[key]] = item[key]
  305. }
  306. })
  307. list.push(obj)
  308. })
  309. const filteredArray = list.map(item => {
  310. if (item.jyxm && item.fhl && item.jl) {
  311. return item
  312. }
  313. return null
  314. }).filter(item => item !== null)
  315. filteredArray.forEach(item => {
  316. list.forEach(el => {
  317. if (el.jyxm === item.jyxm) {
  318. el.fhl = item.fhl
  319. el.jl = item.jl
  320. el.xmfhl = item.xmfhl
  321. }
  322. })
  323. })
  324. this.reagentData = list
  325. this.disabled = true
  326. setTimeout(() => {
  327. this.$nextTick(() => {
  328. this.$refs.reagent && this.$refs.reagent.$forceUpdate()
  329. })
  330. })
  331. this.importTableDialogVisible = false
  332. this.$emit('change-data', 'zuJianShuJu', JSON.stringify([this.reagentData, this.copyDialogData.length, this.disabled]))
  333. })
  334. },
  335. // 当前页码改变
  336. handleCurrentChange (val) {
  337. this.requestPage.pageNo = val
  338. },
  339. // 页码选择器改变
  340. handleSizeChange (val) {
  341. this.requestPage.limit = val
  342. this.requestPage.pageNo = 1
  343. },
  344. showAndHide (data) {
  345. if (data.includes('平行试验') || data.includes('留样再测') || data.includes('对比方案')) {
  346. this.show = true
  347. } else {
  348. this.show = false
  349. }
  350. },
  351. // 配置样品
  352. openDialog () {
  353. this.centerDialogVisible = true
  354. if (this.copyDialogData.length > 0) {
  355. this.dialogData = JSON.parse(JSON.stringify(this.copyDialogData))
  356. } else {
  357. this.dialogData = [
  358. { number: '' },
  359. { number: '' },
  360. { number: '' },
  361. { number: '' },
  362. { number: '' }
  363. ]
  364. }
  365. },
  366. // 生成数据
  367. generateData () {
  368. if (this.copyDialogData.length > 0 && this.ypData.length > 0 && !this.ypFlag) {
  369. if (this.reagentData.length > 0) {
  370. this.$confirm('将重置表格数据,是否确认操作?', '提示', {
  371. confirmButtonText: '确定',
  372. cancelButtonText: '取消',
  373. type: 'warning'
  374. }).then(() => {
  375. // 将配置样品数据填入reagentData
  376. this.initData()
  377. })
  378. } else {
  379. // 将配置样品数据填入reagentData
  380. this.initData()
  381. }
  382. } else {
  383. this.$message.warning('请先配置样品数据')
  384. }
  385. },
  386. // 计算结果
  387. computedResult () {
  388. // computedFlag:true表示里面有空数据或填写不规范数据
  389. let computedFlag = false
  390. this.reagentData.forEach(item => {
  391. if (!item.jsjcdjg || item.jsjcdjg <= 0 || !item.xsjcdjg || item.xsjcdjg <= 0) {
  392. computedFlag = true
  393. }
  394. })
  395. if (!computedFlag && this.reagentData.length > 0) {
  396. this.reagentData.forEach(item => {
  397. item.pq = this.deleteAccuracy(Math.abs(item.xsjcdjg - item.jsjcdjg) / item.jsjcdjg)
  398. item.sfxf = Number(item.pq.replace('%', '')) <= Number(item.xdfw.replace('%', '')) ? '是' : '否'
  399. })
  400. this.ypData.forEach(c => {
  401. const count = this.reagentData.filter(item => item.jyxm === c.jianCeXiangMu && item.sfxf === '是').length
  402. this.reagentData.forEach(item => {
  403. if (item.jyxm === c.jianCeXiangMu) {
  404. item.fhl = this.deleteAccuracy(count / this.copyDialogData.length)
  405. item.jl = Number(item.fhl.replace('%', '')) >= Number(c.xiangMuFuHeLv.replace('%', '')) ? '合格' : '不合格'
  406. }
  407. })
  408. })
  409. this.disabled = true
  410. this.$emit('change-data', 'zuJianShuJu', JSON.stringify([this.reagentData, this.copyDialogData.length, this.disabled]))
  411. } else {
  412. this.$message.warning('试剂测得结果必须大于0且不能为空!')
  413. }
  414. },
  415. initData () {
  416. this.disabled = false
  417. this.reagentData = []
  418. this.ypData.forEach(item => {
  419. this.copyDialogData.forEach(el => {
  420. this.reagentData.push({ jyxm: item.jianCeXiangMu, nd: '', ypbh: el.number, jsjcdjg: '', xsjcdjg: '', pq: '', xdfw: item.xianDingFanWei + '%', sfxf: '', fhl: '', jl: '', xmfhl: item.xiangMuFuHeLv + '%', sjcz: '', yxpq: '' })
  421. })
  422. })
  423. this.$refs.reagent.doLayout()
  424. },
  425. spanMethod ({ row, column, rowIndex, columnIndex }) {
  426. // const rowspan = this.copyDialogData.length || this.spanLength
  427. if (columnIndex === 0 || columnIndex === 10 || columnIndex === 11 || columnIndex === 12) {
  428. const currentValue = row[column.property]
  429. const preRow = this.reagentData[rowIndex - 1]
  430. // 上一行这一列的数据
  431. const preValue = preRow ? preRow[column.property] : null
  432. // 如果当前值和上一行的值相同,则将当前单元格隐藏
  433. // 给第0,10,11,12列对数值相同且是同一个'jyxm'进行表格合并
  434. if (currentValue === preValue && row['jyxm'] === preRow['jyxm']) {
  435. return { rowspan: 0, colspan: 0 }
  436. } else {
  437. let rowspan = 1
  438. // 计算应该合并的行数
  439. for (let i = rowIndex + 1; i < this.reagentData.length; i++) {
  440. const nextRow = this.reagentData[i]
  441. const nextValue = nextRow[column.property]
  442. if (nextValue === currentValue && nextRow['jyxm'] === row['jyxm']) {
  443. rowspan++
  444. } else {
  445. break
  446. }
  447. }
  448. return { rowspan, colspan: 1 }
  449. }
  450. // if (rowIndex % rowspan === 0) {
  451. // return {
  452. // rowspan,
  453. // colspan: 1
  454. // }
  455. // } else {
  456. // return {
  457. // rowspan: 0,
  458. // colspan: 0
  459. // }
  460. // }
  461. }
  462. },
  463. addRow () {
  464. this.dialogData.push({ number: '' })
  465. },
  466. deleteRow (index) {
  467. if (this.dialogData.length === 1) {
  468. this.$message.warning('删除失败,样品不能为空!')
  469. } else {
  470. this.dialogData.splice(index, 1)
  471. }
  472. },
  473. dialogDataConfirm () {
  474. // validateFlag:true 表单校验未通过
  475. this.validateFlag = false
  476. this.dialogData.forEach(item => {
  477. if (!item.number) {
  478. this.validateFlag = true
  479. }
  480. })
  481. if (this.validateFlag) {
  482. this.$message.warning('样品编号未填写完!')
  483. } else {
  484. this.centerDialogVisible = false
  485. this.copyDialogData = JSON.parse(JSON.stringify(this.dialogData))
  486. // 样品配置完自定生成表格数据,先判断样品配置表数据是否全填
  487. if (this.ypData.length > 0 && !this.ypFlag) {
  488. this.initData()
  489. } else {
  490. this.$message.warning('请完成样品参数配置')
  491. }
  492. }
  493. },
  494. // 去除小数*100精度方法
  495. deleteAccuracy (num) {
  496. // 是否带小数点
  497. if ((num.toString()).includes('.')) {
  498. // 保留小数点后面3位
  499. const numArry = num.toFixed(3).toString().split('.')
  500. // 整数位是否大于0
  501. if (numArry[0] > 0) {
  502. return Number(numArry[0] + numArry[1].substring(0, 2) + '.' + numArry[1].substring(2, 3)) + '%'
  503. } else {
  504. // 小数位第一位是否大于0
  505. if (numArry[1][0] > 0) {
  506. return Number(numArry[1].substring(0, 2) + '.' + numArry[1].substring(2, 3)) + '%'
  507. } else {
  508. // 小数位第二位是否大于0
  509. if (numArry[1][1] > 0) {
  510. return Number(numArry[1].substring(1, 2) + '.' + numArry[1].substring(2, 3)) + '%'
  511. } else {
  512. return Number(0 + '.' + numArry[1].substring(2, 3)) + '%'
  513. }
  514. }
  515. }
  516. } else {
  517. return num * 100 + '%'
  518. }
  519. }
  520. }
  521. }
  522. </script>
  523. <style lang="scss" scoped>
  524. .reagentChange{
  525. margin-bottom: 20px;
  526. .button{
  527. display: flex;
  528. justify-content: space-between;
  529. padding: 0px 0px 0px 15px;
  530. background: #f0ffff;
  531. .title {
  532. color: #999;
  533. font-size: 12px;
  534. font-weight: bold;
  535. margin-bottom: 0;
  536. }
  537. .el-button {
  538. margin: 0;
  539. }
  540. }
  541. }
  542. .ragent-dialog ::v-deep .el-dialog__body {
  543. max-height: 300px;
  544. }
  545. .el-dialog__body .ragent-dialog-content {
  546. display: flex;
  547. align-items: center;
  548. justify-content: space-around;
  549. padding: 10px;
  550. }
  551. </style>