消毒机设备
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.

587 lines
16 KiB

2 months ago
2 months ago
2 months ago
3 weeks ago
2 months ago
3 weeks ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
4 weeks ago
2 months ago
2 months ago
2 months ago
3 weeks ago
2 months ago
3 weeks ago
2 months ago
3 weeks ago
3 weeks ago
4 weeks ago
2 months ago
2 months ago
2 months ago
1 month ago
4 weeks ago
2 months ago
4 weeks ago
4 weeks ago
2 months ago
2 months ago
4 weeks ago
3 weeks ago
2 months ago
4 weeks ago
2 months ago
2 months ago
2 months ago
3 weeks ago
4 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
2 months ago
2 months ago
3 weeks ago
3 weeks ago
2 months ago
  1. <script lang="ts" setup>
  2. import { sendCmd, syncSendCmd } from 'apis/system'
  3. import SelectModal from 'components/common/SelectModal/index.vue'
  4. import SoftKeyboard from 'components/common/SoftKeyboard/index.vue'
  5. import { ElMessage } from 'element-plus'
  6. import { formulaNameMap } from 'libs/constant'
  7. import { cloneDeep } from 'lodash'
  8. import { useHomeStore } from 'stores/homeStore'
  9. import { computed, inject, nextTick, onMounted, ref, watch, watchEffect } from 'vue'
  10. import { FtMessage } from '@/libs/message'
  11. import { compareJSON, convertValuesToInt, convertValuesToString } from '@/libs/utils'
  12. import { useFormulaStore } from '@/stores/formulaStore'
  13. /**
  14. * 配方表单组件 - 用于配置和管理各种配方参数
  15. * 支持三种模式主页模式(home)设置模式(setting)和配方管理模式(formula)
  16. * @component
  17. * @props {string} type - 组件使用模式可选值'home' | 'setting' | 'formula'
  18. */
  19. const props = defineProps<{
  20. type: string
  21. formulaNameVisible: boolean
  22. }>()
  23. const homeStore = useHomeStore()
  24. const nameLeng = 10
  25. const formulaStore = useFormulaStore()
  26. const targetInputRef = ref<HTMLInputElement | null>(null)
  27. const isFlip = ref(true)
  28. /**
  29. * 当前表单数据
  30. * 不同模式下有不同的初始值
  31. * - home: 当前选中的配方
  32. * - setting: 默认配方信息
  33. * - formula: 当前选中的配方或默认配方
  34. */
  35. const formData = ref<Record<string, any>>({
  36. ...formulaStore.defaultFormulaInfo,
  37. })
  38. /**
  39. * 软键盘当前输入值
  40. */
  41. const inputValue = ref<string>('')
  42. /**
  43. * 软键盘是否可见
  44. */
  45. const keyboardVisible = ref(false)
  46. /**
  47. * 软键盘类型'text' 'number'
  48. */
  49. const keyboardType = ref<'text' | 'number'>('number')
  50. /**
  51. * 软键盘组件引用
  52. */
  53. const softKeyboardRef = ref()
  54. /**
  55. * 当前聚焦的输入字段名称
  56. */
  57. const focusedInput = ref<string | null>(null)
  58. /**
  59. * 注册孙子组件方法的注入函数
  60. */
  61. const registerGrandsonMethods = inject<(methods: any) => void>('registerGrandsonMethods', () => {})
  62. /**
  63. * 配方配置列表
  64. */
  65. const formulaConfigList = ref(formulaStore.formulaConfigList)
  66. /**
  67. * 日志级别选项列表
  68. */
  69. const options = ref(formulaStore.logLevelOptions)
  70. /**
  71. * 标签单位映射表用于显示各参数的单位
  72. */
  73. const labelUnitMap: Record<string, any> = {
  74. injection_pump_speed: 'g/min',
  75. continued_gs: 'ppm',
  76. stoped_gs: 'ppm',
  77. max_humidity: '%RH',
  78. pre_heat_time_s: '秒',
  79. continued_humi: '%RH',
  80. stoped_humi: '%RH',
  81. continued_satur: '%RS',
  82. stoped_satur: '%RS',
  83. loglevel: 'Log',
  84. }
  85. const currentFormulaItem = ref()
  86. /**
  87. * 组件挂载时注册方法供父组件调用
  88. */
  89. onMounted(() => {
  90. registerGrandsonMethods && registerGrandsonMethods({ getFormData })
  91. })
  92. /**
  93. * 模态框是否打开
  94. */
  95. const isModalOpen = ref(false)
  96. /**
  97. * 打开模态框
  98. */
  99. const openModal = () => {
  100. isModalOpen.value = true
  101. }
  102. const disinfectionState = ref(homeStore.disinfectionState) // 消毒状态
  103. /**
  104. * 监听配方配置列表和表单数据变化
  105. * 根据不同的type属性值初始化表单数据
  106. */
  107. watchEffect(() => {
  108. formulaConfigList.value = formulaStore.formulaConfigList
  109. if (props.type === 'home') {
  110. formData.value = cloneDeep(formulaStore.selectedFormulaInfo) || cloneDeep(formulaStore.defaultFormulaInfo)
  111. }
  112. else if (props.type === 'setting') {
  113. formData.value = cloneDeep(formulaStore.defaultFormulaInfo)
  114. }
  115. else {
  116. if (formulaStore.formulaList.length === 0) {
  117. formData.value = cloneDeep(formulaStore.resetFormulaInfo)
  118. }
  119. else {
  120. formData.value = cloneDeep(formulaStore.currentSelectedFormulaInfo) || cloneDeep(formulaStore.defaultFormulaInfo)
  121. }
  122. }
  123. isFlip.value = formulaStore.flip
  124. // 后端给的数据类型是字符串型,前端需要的是int型,后端开发说(赵贺)后端不好转换,由前端进行转换.
  125. formData.value = convertValuesToInt(formData.value)
  126. })
  127. /**
  128. * 监听软键盘输入值变化更新表单数据
  129. * @param {string | number} newVal - 新的输入值
  130. */
  131. watch(inputValue, (newVal: string | number) => {
  132. if (focusedInput.value) {
  133. if (focusedInput.value !== 'name') {
  134. newVal = Number(newVal)
  135. if (currentFormulaItem.value && newVal > currentFormulaItem.value.val_upper_limit) {
  136. newVal = currentFormulaItem.value.val_upper_limit
  137. }
  138. formData.value[focusedInput.value] = newVal
  139. }
  140. else {
  141. if (newVal && newVal.toString().length > nameLeng) {
  142. inputValue.value = formData.value[focusedInput.value]
  143. return
  144. }
  145. formData.value[focusedInput.value] = newVal
  146. }
  147. }
  148. })
  149. /**
  150. * 获取当前表单数据将值转换为字符串格式
  151. * @returns {Record<string, string>} 转换后的表单数据
  152. */
  153. const getFormData = () => {
  154. return convertValuesToString(formData.value, 'name')
  155. }
  156. /**
  157. * 监听表单数据变化同步更新软键盘输入值
  158. * @param {Record<string, any>} newValue - 新的表单数据
  159. */
  160. watch(
  161. formData,
  162. (newValue) => {
  163. if (focusedInput.value) {
  164. inputValue.value = newValue[focusedInput.value].toString()
  165. }
  166. },
  167. { deep: true },
  168. )
  169. const formRef = ref(null)
  170. /**
  171. * 处理表单提交
  172. * 根据不同的type属性值执行不同的保存逻辑
  173. */
  174. const handleSubmit = () => {
  175. if (props.type !== 'setting' && !formData.value.name) {
  176. FtMessage.warning('请输入配方名称')
  177. return
  178. }
  179. // 表单验证
  180. if (formData.value.name && formData.value.name.length > 20) {
  181. FtMessage.warning('配方名称1-20字符')
  182. return
  183. }
  184. // 配方管理的【确定】
  185. if (props.type === 'formula') {
  186. onSaveFormula()
  187. }
  188. // 设置中的【确定】
  189. if (props.type === 'setting') {
  190. onSaveSetting()
  191. }
  192. }
  193. /**
  194. * @computed 计算属性 - 设备状态判断
  195. * @returns {boolean} - 设备是否处于空闲或已完成状态
  196. * @desc 控制按钮可用状态
  197. */
  198. const deviceState = computed(() => {
  199. return disinfectionState.value.state === 'idle' || disinfectionState.value.state === 'finished'
  200. })
  201. /**
  202. * 保存配方
  203. * 根据是否有formula_id决定是添加新配方还是编辑已有配方
  204. */
  205. const onSaveFormula = () => {
  206. if (formData.value.formula_id) {
  207. // 修改配方
  208. // 判断消毒是否正在执行 且正在执行配方是当前修改配方
  209. if (!deviceState.value && formulaStore.selectedFormulaInfo?.formula_id === formData.value.formula_id) {
  210. ElMessage.warning('禁止修改正在执行的配方信息!')
  211. return
  212. }
  213. const formulaForm: Record<string, any> = convertValuesToString(formData.value, 'name')
  214. onEditFormula(formulaForm.formula_id, formulaForm as Formula.FormulaItem)
  215. }
  216. else {
  217. // 新增配方
  218. onAddFormula()
  219. }
  220. }
  221. /**
  222. * 保存设置
  223. * 比较当前表单数据与默认配方数据的差异只更新有变化的字段
  224. */
  225. const onSaveSetting = async () => {
  226. // 修改默认值
  227. const diff = compareJSON(formulaStore.defaultFormulaInfo, formData.value)
  228. console.log(diff)
  229. const diffKeys = Object.keys(diff)
  230. if (diffKeys.length) {
  231. await Promise.all(
  232. diffKeys.map(async (key) => {
  233. await setSettingFormulaConfig(key, diff[key].newVal || diff[key].obj2)
  234. }),
  235. )
  236. FtMessage.success('配方修改成功')
  237. }
  238. }
  239. /**
  240. * 设置配方配置项值
  241. * @param {string} settingName - 设置名称
  242. * @param {string} settingVal - 设置值
  243. * @returns {Promise<void>}
  244. */
  245. const setSettingFormulaConfig = async (settingName: string, settingVal: string) => {
  246. await sendCmd({
  247. className: 'SettingMgrService',
  248. fnName: 'setSettingVal',
  249. params: {
  250. settingName,
  251. settingVal: settingVal?.toString(),
  252. },
  253. })
  254. formulaStore.getFormualDefaultData()
  255. }
  256. /**
  257. * 添加新配方
  258. * 先调用API创建新配方然后更新配方数据
  259. */
  260. const onAddFormula = () => {
  261. const params = {
  262. className: 'SettingMgrService',
  263. fnName: 'addNewFormula',
  264. }
  265. syncSendCmd(params).then((res) => {
  266. if (res.ackcode === 0) {
  267. const item = res.rely
  268. const formulaForm: Record<string, any> = convertValuesToString(formData.value, 'name')
  269. // formulaForm.name = item.name
  270. formulaForm.formula_id = item.formula_id
  271. onEditFormula(item.formula_id, formulaForm as Formula.FormulaItem)
  272. formulaStore.initFormulaList()
  273. }
  274. })
  275. }
  276. /**
  277. * 编辑配方
  278. * @param {string} formula_id - 配方ID
  279. * @param {Formula.FormulaItem} formulaForm - 配方表单数据
  280. */
  281. const onEditFormula = (formula_id: string, formulaForm: Formula.FormulaItem) => {
  282. const editParams = {
  283. className: 'SettingMgrService',
  284. fnName: 'updateFormula',
  285. params: {
  286. formula_id,
  287. formula: cloneDeep(formulaForm),
  288. },
  289. }
  290. syncSendCmd(editParams).then(() => {
  291. FtMessage.success('操作成功')
  292. formulaStore.initFormulaList()
  293. // formData.value = formulaForm
  294. formulaStore.updateSelectedFormulaData(formulaForm)
  295. if (
  296. formulaStore.selectedFormulaInfo !== null
  297. && formulaStore.selectedFormulaInfo.formula_id === formulaForm.formula_id
  298. ) {
  299. formulaStore.updateSelectedFormulaDataByList(formulaForm)
  300. }
  301. })
  302. }
  303. /**
  304. * 打开软键盘
  305. * @param {Event} e - 事件对象
  306. * @param item
  307. */
  308. const openKeyboard = (e: any, item: Formula.FormulaItem) => {
  309. setTimeout(() => {
  310. keyboardVisible.value = true
  311. const labelName: string = e.target.name
  312. openKeyboardType(labelName)
  313. const formValue = formData.value[labelName]
  314. inputValue.value = formValue.toString()
  315. focusedInput.value = e.target.name
  316. currentFormulaItem.value = item
  317. const inputDom = e.target as HTMLInputElement
  318. targetInputRef.value = inputDom
  319. }, 100)
  320. }
  321. /**
  322. * 取消操作重置配方数据
  323. */
  324. // const handleCancel = () => {
  325. // formulaStore.initFormulaData()
  326. // }
  327. /**
  328. * 恢复默认设置
  329. */
  330. const handleResetDefault = async () => {
  331. await sendCmd({
  332. className: 'SettingMgrService',
  333. fnName: 'factoryResetSettings',
  334. })
  335. await formulaStore.getFormualDefaultData()
  336. }
  337. /**
  338. * 确认输入值
  339. * @param {string} value - 输入值
  340. */
  341. const handleConfirm = (value: string) => {
  342. console.log('确认输入:', value)
  343. }
  344. /**
  345. * 确认日志级别选择
  346. * @param {any} value - 选择的值
  347. */
  348. const handleLogConfirm = (value: any) => {
  349. isModalOpen.value = false
  350. formData.value.loglevel = value
  351. formulaStore.loglevel = value
  352. }
  353. /**
  354. * 取消日志级别选择
  355. */
  356. const handleLogCancel = () => {
  357. isModalOpen.value = false
  358. }
  359. /**
  360. * 根据标签名称确定软键盘类型
  361. * @param {string} labelName - 标签名称
  362. */
  363. const openKeyboardType = (labelName: string) => {
  364. keyboardType.value = labelName === 'name' ? 'text' : 'number'
  365. }
  366. const size = 'default'
  367. // 新增配方方法
  368. const addFormula = async () => {
  369. const formulaInfo = cloneDeep(formulaStore.defaultFormulaInfo)
  370. console.log(formulaInfo)
  371. formData.value = formulaInfo
  372. await nextTick() // 等待DOM更新
  373. // 移除左侧列表的选中
  374. formData.value.name = ''
  375. formData.value.formula_id = null
  376. console.log('子组件方法被调用')
  377. }
  378. // 暴露方法给父组件
  379. defineExpose({
  380. addFormula,
  381. })
  382. </script>
  383. <template>
  384. <transition name="slide-right">
  385. <div v-if="isFlip" class="formula-form">
  386. <el-form
  387. ref="formRef"
  388. :disabled="type === 'home'"
  389. :model="formData"
  390. label-width="auto"
  391. label-position="right"
  392. :size="size"
  393. inline
  394. >
  395. <el-form-item v-if="type !== 'setting' && formulaNameVisible" label="配方名称" style="width: 93%">
  396. <el-input
  397. v-model="formData.name"
  398. v-prevent-keyboard
  399. :rules="[
  400. { required: true, message: '配方名称必填', trigger: 'blur' },
  401. { min: '1', max: '20', message: '配方名称1-20个字符', trigger: 'blur' },
  402. ]"
  403. name="name"
  404. prop="name"
  405. placeholder="配方名称"
  406. :disabled="type === 'home'"
  407. @focus="openKeyboard"
  408. />
  409. </el-form-item>
  410. <el-form-item
  411. v-for="item in formulaConfigList"
  412. :key="item.setting_id"
  413. :label="formulaNameMap[item.setting_id]"
  414. style="width: 50%"
  415. >
  416. <template v-if="item.val_type === 'int'">
  417. <el-input
  418. v-model.number="formData[item.setting_id]"
  419. v-prevent-keyboard
  420. style="width: 80%"
  421. type="number"
  422. :name="item.setting_id"
  423. :controls="false"
  424. :disabled="!item.is_visible_in_setting_page"
  425. @focus="e => openKeyboard(e, item)"
  426. >
  427. <template v-if="labelUnitMap[item.setting_id]" #append>
  428. {{ labelUnitMap[item.setting_id] }}
  429. </template>
  430. </el-input>
  431. </template>
  432. <template v-else-if="item.val_type === 'enum'">
  433. <el-input
  434. v-model="formData[item.setting_id]"
  435. v-prevent-keyboard
  436. style="width: 80%"
  437. placeholder="请选择"
  438. readonly
  439. @focus="openModal"
  440. >
  441. <template #append>
  442. {{ labelUnitMap[item.setting_id] }}
  443. </template>
  444. </el-input>
  445. </template>
  446. <template v-else-if="item.val_type === 'boolean'">
  447. <el-radio-group v-model="formData[item.setting_id]" :disabled="!item.is_visible_in_setting_page">
  448. <el-radio :label="true">
  449. </el-radio>
  450. <el-radio :label="false">
  451. </el-radio>
  452. </el-radio-group>
  453. </template>
  454. </el-form-item>
  455. </el-form>
  456. <div v-if="type !== 'home'" :style="{ marginLeft: '33%' }">
  457. <slot name="formulaBtn">
  458. <div class="default-btn">
  459. <el-button v-if="type === 'setting'" class="config-btn" @click="handleResetDefault">
  460. 恢复默认值
  461. </el-button>
  462. <el-button type="primary" class="config-btn" @click="handleSubmit">
  463. 确定
  464. </el-button>
  465. </div>
  466. </slot>
  467. </div>
  468. <Teleport to="body">
  469. <SoftKeyboard
  470. ref="softKeyboardRef"
  471. v-model="inputValue"
  472. :is-visible="keyboardVisible"
  473. :keyboard-type="keyboardType"
  474. :target-input="targetInputRef"
  475. @confirm="handleConfirm"
  476. @update-keyboard-visible="visible => (keyboardVisible = visible)"
  477. @close="keyboardVisible = false"
  478. />
  479. </Teleport>
  480. <SelectModal
  481. v-if="isModalOpen"
  482. :options="options"
  483. :selected-value="formData.loglevel"
  484. placeholder="请选择"
  485. @confirm="handleLogConfirm"
  486. @cancel="handleLogCancel"
  487. />
  488. </div>
  489. </transition>
  490. </template>
  491. <style lang="scss" scoped>
  492. .formula-form {
  493. font-size: 20px !important;
  494. padding: 5px;
  495. padding-left: 15px;
  496. align-items: center;
  497. .default-btn {
  498. margin-top: 1rem;
  499. }
  500. .config-btn {
  501. height: 3rem;
  502. width: 8rem;
  503. }
  504. }
  505. .formula-form-item {
  506. display: grid;
  507. grid-template-columns: 1fr 1fr;
  508. }
  509. .formData-input-config {
  510. width: 10vw;
  511. }
  512. :deep(.el-input__inner) {
  513. text-align: left;
  514. height: 40px;
  515. }
  516. :deep(.el-form-item) {
  517. margin-right: 0;
  518. align-items: center;
  519. }
  520. /* 进入动画的初始状态 */
  521. .slide-right-enter-from {
  522. transform: translateX(100%);
  523. }
  524. /* 进入动画的结束状态(也可以理解为激活状态) */
  525. .slide-right-enter-to {
  526. transform: translateX(0);
  527. }
  528. /* 进入动画的过渡曲线等 */
  529. .slide-right-enter-active {
  530. transition: transform 0.3s ease-in-out;
  531. }
  532. /* 离开动画的初始状态(激活状态) */
  533. .slide-right-leave-from {
  534. transform: translateX(0);
  535. }
  536. /* 离开动画的结束状态 */
  537. .slide-right-leave-to {
  538. transform: translateX(100%);
  539. }
  540. /* 离开动画的过渡曲线等 */
  541. .slide-right-leave-active {
  542. transition: transform 0.3s ease-in-out;
  543. }
  544. </style>