华西海圻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.

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