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

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