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

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