华西海圻ELN前端工程
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.

609 lines
17 KiB

  1. <template>
  2. <div class="step-container">
  3. <el-button v-if="isShowAddStep()" type="primary" @click="addStep" icon="el-icon-plus">添加步骤</el-button>
  4. <div class="step-list">
  5. <div v-for="(step, index) in steps" :key="step.id" class="step-list-item">
  6. <div class="step-content">
  7. <span class="step-title">{{ index + 1 }}</span>
  8. <HandleFormItem type="select" placeholder="请选择" class="step-type-select" :item="stepSelectConfig"
  9. v-model="step.type" @change="onTypeChange(index)" />
  10. <!-- 根据步骤类型显示对应的表单 -->
  11. <!-- 根据步骤类型显示对应的表单 -->
  12. <component class="flex1" :is="getStepComponent(step.type)" :formData="step.formData"
  13. @update="onFormUpdate(index, $event)" :ref="'stepCompRef_' + index">
  14. </component>
  15. <div class="step-header-item">
  16. <el-popconfirm
  17. @confirm="removeStep(index)"
  18. title="确定删除当前步骤吗?"
  19. >
  20. <el-button type="text" slot="reference" icon="el-icon-delete"
  21. class="delete-btn"></el-button>
  22. </el-popconfirm>
  23. </div>
  24. </div>
  25. </div>
  26. </div>
  27. </div>
  28. </template>
  29. <script>
  30. import HandleFormItem from './HandleFormItem.vue';
  31. import Czdd from './StepComponents/ry/czdd.vue';//溶液-操作地点
  32. import Czhj from './StepComponents/ry/czhj.vue';//溶液-操作方法
  33. import Xzrq from './StepComponents/ry/xzrq.vue';//溶液-选择容器
  34. import Jrry from './StepComponents/ry/jrry.vue';//溶液-加入溶液
  35. import Tpjydd from './StepComponents/ry/tpjydd.vue';//溶液-天平校验(单点)
  36. import Tpjysd from './StepComponents/ry/tpjysd.vue';//溶液-天平校验(双点)
  37. import Qywz from './StepComponents/ry/qywz.vue';//溶液-取用物质
  38. import Clfcz from './StepComponents/ry/clfcz.vue';//溶液-称量(非传值)
  39. import Clcz from './StepComponents/ry/clcz.vue';//溶液-称量(传值)
  40. import Bdtj from './StepComponents/ry/bdtj.vue';//溶液-标定(体积)
  41. import Bdzl from './StepComponents/ry/bdzl.vue';//溶液-标定(质量)
  42. import Tjphcz from './StepComponents/ry/tjphcz.vue';//溶液-调节PH(传值)
  43. import Tjphfcz from './StepComponents/ry/tjphfcz.vue';//溶液-调节PH(非传值)
  44. import Lx from './StepComponents/ry/lx.vue';//溶液-离心
  45. import Hwhy from './StepComponents/ry/hwhy.vue';//溶液-恒温混匀
  46. import Zyhy from './StepComponents/ry/zyhy.vue';//溶液-振摇混匀
  47. import Wxhy from "./StepComponents/ry/wxhy.vue";//溶液-涡旋混匀
  48. import Ddhy from "./StepComponents/ry/ddhy.vue";//溶液-颠倒混匀
  49. import Ym from "./StepComponents/ry/ym.vue";//溶液-研磨
  50. import Jb from "./StepComponents/ry/jb.vue";//溶液-搅拌
  51. import Jrjb from "./StepComponents/ry/jrjb.vue";//溶液-加热搅拌
  52. import Cs from "./StepComponents/ry/cs.vue";//溶液-超声
  53. import Sy from "./StepComponents/ry/sy.vue";//溶液-水浴
  54. import Dc from "./StepComponents/ry/dc.vue";//溶液-氮吹
  55. import Jd from "./StepComponents/ry/jd.vue";//溶液-解冻
  56. import Jz from "./StepComponents/ry/jz.vue";//溶液-静置
  57. import Glzd from "./StepComponents/ry/glzd.vue";//溶液-过滤(自动)
  58. import Glsd from "./StepComponents/ry/glsd.vue";//溶液-过滤(手动)
  59. import Fy from "./StepComponents/ry/fy.vue";//溶液-孵育
  60. import Qcyy from "./StepComponents/ry/qcyy.vue";//溶液-取出原药
  61. import Frdrq from "./StepComponents/ry/frdrq.vue";//溶液-复溶(多容器)
  62. import Fr from "./StepComponents/ry/fr.vue";//溶液-复溶
  63. import Hb from "./StepComponents/ry/hb.vue";//溶液-合并
  64. import Rs from "./StepComponents/ry/rs.vue";//溶液-染色
  65. import Js from "./StepComponents/ry/js.vue";//溶液-计数
  66. import Mj from "./StepComponents/ry/mj.vue";//溶液-灭菌
  67. import Fs from "./StepComponents/ry/fs.vue";//溶液-复苏
  68. import Fb from "./StepComponents/ry/fb.vue";//溶液-封板
  69. const stepTypes = [
  70. { label: '操作地点', value: 'czdd' },
  71. { label: '操作方法', value: 'czhj' },
  72. { label: '选择容器', value: 'xzrq' },
  73. { label: '加入溶液', value: 'jrry' },
  74. { label: '天平校验(单点)', value: 'tpjydd' },
  75. { label: '天平校验(双点)', value: 'tpjysd' },
  76. { label: '取用物质', value: 'qywz' },
  77. { label: '称量(非传值)', value: 'clfcz' },
  78. { label: '称量(传值)', value: 'clcz' },
  79. { label: '标定(体积)', value: 'bdtj' },
  80. { label: '标定(质量)', value: 'bdzl' },
  81. { label: '调节PH(传值)', value: 'tjphcz' },
  82. { label: '调节PH(非传值)', value: 'tjphfcz' },
  83. { label: '离心', value: 'lx' },
  84. { label: '恒温混匀', value: 'hwhy' },
  85. { label: '振摇混匀', value: 'zyhy' },
  86. { label: '涡旋混匀', value: 'wxhy' },
  87. { label: '颠倒混匀', value: 'ddhy' },
  88. { label: '研磨', value: 'ym' },
  89. { label: '搅拌', value: 'jb' },
  90. { label: '加热搅拌', value: 'jrjb' },
  91. { label: '超声', value: 'cs' },
  92. { label: '水浴', value: 'sy' },
  93. { label: '氮吹', value: 'dc' },
  94. { label: '解冻', value: 'jd' },
  95. { label: '静置', value: 'jz' },
  96. { label: '过滤(自动)', value: 'glzd' },
  97. { label: '过滤(手动)', value: 'glsd' },
  98. { label: '孵育', value: 'fy' },
  99. { label: '取出原药', value: 'qcyy' },
  100. { label: '复溶(多容器)', value: 'frdrq' },
  101. { label: '复溶', value: 'fr' },
  102. { label: '合并', value: 'hb' },
  103. { label: '染色', value: 'rs' },
  104. { label: '计数', value: 'js' },
  105. { label: '灭菌', value: 'mj' },
  106. { label: '复苏', value: 'fs' },
  107. { label: '封板', value: 'fb' },
  108. ];
  109. export default {
  110. inject: ['templateFillType'],
  111. name: 'Step',
  112. props: {
  113. formData: {
  114. type: Array,
  115. default: () => []
  116. }
  117. },
  118. data() {
  119. return {
  120. stepSelectConfig: {
  121. options: stepTypes,
  122. fillType: "preFill",
  123. placeholder: "请选择步骤类型"
  124. },
  125. steps: [],
  126. stepId: 1,
  127. componentMap: null
  128. }
  129. },
  130. components: {
  131. HandleFormItem,
  132. Czdd,
  133. Czhj,
  134. Xzrq,
  135. Jrry,
  136. Tpjydd,
  137. Tpjysd,
  138. Qywz,
  139. Clfcz,
  140. Clcz,
  141. Bdtj,
  142. Bdzl,
  143. Tjphcz,
  144. Tjphfcz,
  145. Lx,
  146. Hwhy,
  147. Zyhy,
  148. Wxhy,
  149. Ddhy,
  150. Ym,
  151. Jb,
  152. Jrjb,
  153. Cs,
  154. Sy,
  155. Dc,
  156. Jd,
  157. Jz,
  158. Glzd,
  159. Glsd,
  160. Fy,
  161. Qcyy,
  162. Frdrq,
  163. Fr,
  164. Hb,
  165. Rs,
  166. Js,
  167. Mj,
  168. Fs,
  169. Fb,
  170. },
  171. computed: {
  172. stepComponentMap() {
  173. if (!this.componentMap) {
  174. this.componentMap = {
  175. 'czdd': 'Czdd',
  176. 'czhj': 'Czhj',
  177. 'xzrq': 'Xzrq',
  178. 'jrry': 'Jrry',
  179. 'tpjydd': 'Tpjydd',
  180. 'tpjysd': 'Tpjysd',
  181. 'qywz': 'Qywz',
  182. 'clfcz': 'Clfcz',
  183. 'clcz': 'Clcz',
  184. 'bdtj': 'Bdtj',
  185. 'bdzl': 'Bdzl',
  186. 'tjphcz': 'Tjphcz',
  187. 'tjphfcz': 'Tjphfcz',
  188. 'lx': 'Lx',
  189. 'hwhy': 'Hwhy',
  190. 'zyhy': 'Zyhy',
  191. 'wxhy': 'Wxhy',
  192. 'ddhy': 'Ddhy',
  193. 'ym': 'Ym',
  194. 'jb': 'Jb',
  195. 'jrjb': 'Jrjb',
  196. 'sy': 'Sy',
  197. 'cs': 'Cs',
  198. 'dc': 'Dc',
  199. 'jd': 'Jd',
  200. 'jz': 'Jz',
  201. 'glzd': 'Glzd',
  202. 'glsd': 'Glsd',
  203. 'fy': 'Fy',
  204. 'qcyy': 'Qcyy',
  205. 'frdrq': 'Frdrq',
  206. 'fr': 'Fr',
  207. 'hb': 'Hb',
  208. 'rs': 'Rs',
  209. 'js': 'Js',
  210. 'mj': 'Mj',
  211. 'fs': 'Fs',
  212. 'fb': 'Fb',
  213. }
  214. }
  215. return this.componentMap
  216. }
  217. },
  218. created() {
  219. // // 初始化步骤数据
  220. // if (this.value && this.value.length > 0) {
  221. // this.steps = this.value.map((step) => ({
  222. // id: this.stepId++,
  223. // type: step.type || '',
  224. // formData: step.formData || {}
  225. // }))
  226. // } else {
  227. // // 默认添加一个步骤
  228. // this.addStep()
  229. // }
  230. },
  231. watch: {
  232. // steps: {
  233. // handler(newVal) {
  234. // this.$emit('input', newVal.map(step => ({
  235. // type: step.type,
  236. // formData: step.formData
  237. // })))
  238. // },
  239. // deep: true
  240. // },
  241. formData: {
  242. handler(newVal) {
  243. if (!newVal || newVal.length === 0) return
  244. this.steps = newVal;
  245. },
  246. deep: true,
  247. immediate: true
  248. }
  249. },
  250. methods: {
  251. isShowAddStep() {
  252. return this.templateFillType === 'preFill';
  253. },
  254. addStep() {
  255. try {
  256. this.steps.push({
  257. id: this.stepId++,
  258. type: '',
  259. formData: {}
  260. })
  261. this.$emit('step-added', this.steps.length)
  262. } catch (error) {
  263. console.error('添加步骤失败:', error)
  264. this.$message.error('添加步骤失败,请重试')
  265. }
  266. },
  267. removeStep(index) {
  268. if (this.steps.length > 1) {
  269. const removedStep = this.steps.splice(index, 1)[0]
  270. this.$emit('step-removed', { index, step: removedStep, remaining: this.steps.length })
  271. } else {
  272. this.$message.warning('至少需要保留一个步骤')
  273. }
  274. },
  275. onTypeChange(index) {
  276. // 切换步骤类型时重置表单数据,并确保数据更新
  277. const oldType = this.steps[index].type
  278. this.$set(this.steps[index], 'formData', {})
  279. // 可选:添加类型变化的回调
  280. this.$emit('step-type-changed', {
  281. index,
  282. newType: this.steps[index].type,
  283. oldType
  284. })
  285. },
  286. onFormUpdate(stepIndex, formData) {
  287. this.steps[stepIndex].formData = formData
  288. },
  289. getStepComponent(type) {
  290. // 使用计算属性中的映射,提高性能
  291. return this.stepComponentMap[type]
  292. },
  293. // 公共方法:获取所有步骤数据
  294. getFormData() {
  295. return new Promise(async (resolve, reject) => {
  296. // 检查是否有步骤数据
  297. if (this.steps.length === 0) {
  298. // this.$message.error(this.$t('template.common.addStepError'))
  299. reject({ errorType: "step" });
  300. return
  301. }
  302. try {
  303. const stepData = await Promise.all(
  304. this.steps.map(async (step, index) => {
  305. const stepComponentRef = this.$refs[`stepCompRef_${index}`];
  306. if (stepComponentRef && stepComponentRef.length > 0) {
  307. try {
  308. const stepFormData = await stepComponentRef[0].getFormData();
  309. return { type: step.type, formData: stepFormData }
  310. } catch (error) {
  311. // 如果某个步骤的getFormData方法失败,抛出错误
  312. throw error;
  313. }
  314. } else {
  315. // 如果没有找到组件引用,返回原始数据
  316. return { type: step.type, formData: step.formData }
  317. }
  318. })
  319. );
  320. resolve({ stepData });
  321. } catch (error) {
  322. reject(error);
  323. }
  324. })
  325. },
  326. getStepResource(){
  327. const sj = [];
  328. let yq = [];
  329. const stepData = this.steps.map((step, index) => {
  330. const stepComponentRef = this.$refs[`stepCompRef_${index}`];
  331. if(stepComponentRef && stepComponentRef.length > 0){
  332. const {sjResource,yqResource} = this.$refs[`stepCompRef_${index}`][0]?.getSjResource();
  333. if(sjResource && sjResource.length > 0){
  334. sj.push(...sjResource);
  335. }
  336. if(yqResource && yqResource.length > 0){
  337. yq.push(...yqResource);
  338. }
  339. }
  340. })
  341. // 对sj数组根据type和value值去重,并将yl按单位换算后累加
  342. const uniqueSj = [];
  343. const sjMap = new Map();
  344. // 体积单位转换为基本单位L的倍数
  345. const volumeUnits = {
  346. 'pL': 1e-12,
  347. 'nL': 1e-9,
  348. 'uL': 1e-6,
  349. 'mL': 1e-3,
  350. 'L': 1
  351. };
  352. // 质量单位转换为基本单位g的倍数
  353. const massUnits = {
  354. 'pg': 1e-12,
  355. 'ng': 1e-9,
  356. 'ug': 1e-6,
  357. 'mg': 1e-3,
  358. 'g': 1,
  359. 'kg': 1e3
  360. };
  361. for(const item of sj) {
  362. const key = `${item.type}_${item.value}`;
  363. console.log(item,"item")
  364. if(sjMap.has(key)) {
  365. // 如果已存在相同type和value的项,累加yl值
  366. const existingItem = sjMap.get(key);
  367. console.log(existingItem,"existingItem")
  368. // 根据类型选择合适的单位转换
  369. let currentItemYlInBaseUnit, existingItemYlInBaseUnit;
  370. if(item.type === '1') {
  371. // 体积单位转换
  372. const currentItemYl = isNaN(parseFloat(item.yl)) ? 0 : parseFloat(item.yl);
  373. const existingItemYl = isNaN(parseFloat(existingItem.yl)) ? 0 : parseFloat(existingItem.yl);
  374. currentItemYlInBaseUnit = currentItemYl * volumeUnits[item.dw] || 0;
  375. existingItemYlInBaseUnit = existingItemYl * volumeUnits[existingItem.dw] || 0;
  376. } else if(item.type === '7') {
  377. // 质量单位转换
  378. const currentItemYl = isNaN(parseFloat(item.yl)) ? 0 : parseFloat(item.yl);
  379. const existingItemYl = isNaN(parseFloat(existingItem.yl)) ? 0 : parseFloat(existingItem.yl);
  380. currentItemYlInBaseUnit = currentItemYl * massUnits[item.dw] || 0;
  381. existingItemYlInBaseUnit = existingItemYl * massUnits[existingItem.dw] || 0;
  382. } else {
  383. // 其他类型暂不处理单位转换,直接相加
  384. const currentItemYl = isNaN(parseFloat(item.yl)) ? 0 : parseFloat(item.yl);
  385. const existingItemYl = isNaN(parseFloat(existingItem.yl)) ? 0 : parseFloat(existingItem.yl);
  386. currentItemYlInBaseUnit = currentItemYl || 0;
  387. existingItemYlInBaseUnit = existingItemYl || 0;
  388. }
  389. // 计算总和
  390. const totalYlInBaseUnit = currentItemYlInBaseUnit + existingItemYlInBaseUnit;
  391. // 更新existingItem的yl值,保持使用第一个项目的单位作为基准单位
  392. if(item.type === '1') {
  393. existingItem.yl = (totalYlInBaseUnit / volumeUnits[existingItem.dw]).toString();
  394. } else if(item.type === '7') {
  395. existingItem.yl = (totalYlInBaseUnit / massUnits[existingItem.dw]).toString();
  396. } else {
  397. existingItem.yl = totalYlInBaseUnit.toString();
  398. }
  399. } else {
  400. // 如果不存在,添加新项
  401. sjMap.set(key, {...item});
  402. }
  403. }
  404. // 将Map中的值转换回数组
  405. sj.length = 0; // 清空原数组
  406. for(const value of sjMap.values()) {
  407. sj.push(value);
  408. }
  409. // 对yq数组根据value去重
  410. yq = yq.filter((item, index, self) =>
  411. self.findIndex(obj => obj.value === item.value) === index
  412. );
  413. console.log(sj,yq,"stepsResource")
  414. return { sjResource: sj, yqResource: yq };
  415. },
  416. // 直接获取表单数据,不做校验
  417. getFilledFormData() {
  418. const stepData = this.steps.map((step, index) => {
  419. const stepComponentRef = this.$refs[`stepCompRef_${index}`];
  420. if(stepComponentRef && stepComponentRef.length > 0){
  421. const stepFormData = this.$refs[`stepCompRef_${index}`][0]?.getFilledFormData();
  422. return { type: step.type, formData: stepFormData }
  423. }else{
  424. return { type: step.type, formData: step.formData }
  425. }
  426. })
  427. return { stepData }
  428. },
  429. // 公共方法:设置步骤数据
  430. setStepData(data) {
  431. if (Array.isArray(data)) {
  432. this.steps = data.map(step => ({
  433. id: this.stepId++,
  434. type: step.type || '',
  435. formData: step.formData || {}
  436. }))
  437. }
  438. },
  439. // 公共方法:重置所有步骤
  440. resetSteps() {
  441. this.steps = [{
  442. id: this.stepId++,
  443. type: '',
  444. formData: {}
  445. }]
  446. this.$emit('steps-reset')
  447. },
  448. // 公共方法:获取指定步骤的数据
  449. getStepDataByIndex(index) {
  450. if (index >= 0 && index < this.steps.length) {
  451. return {
  452. type: this.steps[index].type,
  453. formData: this.steps[index].formData
  454. }
  455. }
  456. return null
  457. },
  458. // 公共方法:验证所有步骤
  459. async validateSteps() {
  460. const errors = []
  461. for (let index = 0; index < this.steps.length; index++) {
  462. const step = this.steps[index];
  463. if (!step.type) {
  464. errors.push(`步骤 ${index + 1}: 请选择步骤类型`)
  465. continue;
  466. }
  467. // 获取当前步骤的组件实例
  468. const stepComponentRef = this.$refs[`stepCompRef_${index}`];
  469. if (stepComponentRef && stepComponentRef.length > 0) {
  470. try {
  471. // 调用子组件的getFormData方法进行验证(不抛出错误,只验证)
  472. await stepComponentRef[0].validateAndMarkRed();
  473. } catch (error) {
  474. // validateAndMarkRed方法不应该抛出错误,但如果有的话捕获它
  475. console.error(`步骤 ${index + 1} 验证时出错:`, error);
  476. }
  477. }
  478. }
  479. return {
  480. isValid: errors.length === 0,
  481. errors
  482. }
  483. },
  484. // 公共方法:批量导入步骤数据
  485. importSteps(stepDataArray) {
  486. if (Array.isArray(stepDataArray)) {
  487. this.steps = stepDataArray.map((step, index) => ({
  488. id: this.stepId++,
  489. type: step.type || '',
  490. formData: step.formData || {}
  491. }))
  492. this.$emit('steps-imported', this.steps.length)
  493. }
  494. },
  495. // 公共方法:获取步骤统计信息
  496. getStepStatistics() {
  497. const stats = {
  498. total: this.steps.length,
  499. byType: {},
  500. filled: 0
  501. }
  502. this.steps.forEach(step => {
  503. // 统计各类型数量
  504. if (step.type) {
  505. stats.byType[step.type] = (stats.byType[step.type] || 0) + 1
  506. }
  507. // 统计已填写的步骤
  508. if (step.type && Object.keys(step.formData).length > 0) {
  509. stats.filled++
  510. }
  511. })
  512. return stats
  513. }
  514. },
  515. }
  516. </script>
  517. <style lang="scss" scoped>
  518. .step-container {
  519. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  520. margin-top: 24px;
  521. padding: 24px;
  522. border-radius: 5px 5px;
  523. .step-header {
  524. margin-bottom: 20px;
  525. padding: 15px;
  526. background: #f5f7fa;
  527. border-radius: 6px;
  528. }
  529. .flex1 {
  530. flex:1
  531. }
  532. .step-list {
  533. .step-list-item {
  534. margin-top: 10px;
  535. border-radius: 6px;
  536. overflow: hidden;
  537. .step-title {
  538. margin-right: 10px;
  539. margin-top: 6px;
  540. }
  541. .step-type-select {
  542. width: 200px;
  543. margin-right: 10px;
  544. max-width: 200px;
  545. }
  546. .delete-btn {
  547. color: #f56c6c;
  548. &:hover {
  549. color: #f78989;
  550. }
  551. &:disabled {
  552. color: #c0c4cc;
  553. }
  554. }
  555. .step-content {
  556. display: flex;
  557. align-items: flex-start;
  558. }
  559. }
  560. }
  561. }
  562. </style>