A8000
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.

628 lines
15 KiB

8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
  1. <template>
  2. <div class="device-management">
  3. <div class="setting-item">
  4. <span class="label">日期与时间</span>
  5. <el-date-picker
  6. class="date-input"
  7. v-model="time"
  8. :key="pickerKey"
  9. type="datetime"
  10. placeholder="修改日期与时间"
  11. format="YYYY-MM-DD HH:mm:ss"
  12. value-format="YYYY-MM-DD HH:mm:ss"
  13. @change="(val) => updateSetting('date', val)"
  14. @focus="pauseTimer"
  15. @blur="resumeTimer"
  16. />
  17. </div>
  18. <!-- <div class="setting-item">
  19. <span class="label">时间</span>
  20. <span class="value">{{ time }}</span>
  21. </div> -->
  22. <div class="setting-item">
  23. <span class="label">语言</span>
  24. <div class="options">
  25. <button
  26. v-for="lang in languages"
  27. :key="lang.value"
  28. :class="{ active: settings.language === lang.value }"
  29. @click="updateSetting('language', lang.value)"
  30. :disabled="lang.value == 'ko_KR'"
  31. >
  32. {{ lang.label }}
  33. </button>
  34. </div>
  35. </div>
  36. <div class="setting-item-box">
  37. <div class="setting-item setting-item-no-border">
  38. <span class="label">整体温度控制</span>
  39. <div class="options">
  40. <input
  41. style="width: 150px"
  42. v-model="settings.allTemperature"
  43. type="number"
  44. @focus="showKeyboard('number', 1)"
  45. readonly
  46. />
  47. <button
  48. @click="updateSetting('allTemperature', settings.allTemperature)"
  49. >
  50. 设置
  51. </button>
  52. </div>
  53. </div>
  54. <div class="setting-item setting-item-no-border">
  55. <span class="label">孵育盘温度 <el-tag type="primary" class="option-tag"> {{ deviceStore.sensorState?.incubateBoxTemperature }}</el-tag></span>
  56. <div class="options">
  57. <input
  58. style="width: 150px"
  59. v-model="settings.incubateBoxTemperature"
  60. type="number"
  61. @focus="showKeyboard('number', 2)"
  62. readonly
  63. />
  64. <button
  65. @click="
  66. updateSetting(
  67. 'incubateBoxTemperature',
  68. settings.incubateBoxTemperature,
  69. )
  70. "
  71. >
  72. 设置
  73. </button>
  74. </div>
  75. </div>
  76. <div class="setting-item setting-item-no-border">
  77. <span class="label">板夹区温度 <el-tag type="primary" class="option-tag"> {{ deviceStore.sensorState?.pboxTemperature }}</el-tag></span>
  78. <div class="options">
  79. <input
  80. style="width: 150px"
  81. v-model="settings.plateBoxTemperature"
  82. type="number"
  83. @focus="showKeyboard('number', 3)"
  84. readonly
  85. />
  86. <button
  87. @click="
  88. updateSetting('plateBoxTemperature', settings.plateBoxTemperature)
  89. "
  90. >
  91. 设置
  92. </button>
  93. </div>
  94. </div>
  95. </div>
  96. <div class="setting-item" style="border-radius: 0">
  97. <span class="label">DHCP</span>
  98. <div class="options">
  99. <button
  100. :class="{
  101. active: settings.DHCP,
  102. }"
  103. @click="updateSetting('DHCP', true)"
  104. >
  105. 开启
  106. </button>
  107. <button
  108. :class="{
  109. active: !settings.DHCP,
  110. }"
  111. @click="updateSetting('DHCP', false)"
  112. >
  113. 关闭
  114. </button>
  115. </div>
  116. </div>
  117. <div v-if="!settings.DHCP" class="setting-item">
  118. <span class="label">Local IP</span>
  119. <div class="options">
  120. <input
  121. style="min-width: 250px"
  122. v-model="settings.localIp"
  123. type="text"
  124. @focus="showKeyboard(undefined, 4)"
  125. readonly
  126. />
  127. <button @click="updateSetting('localIP', settings.localIp)">
  128. 设置
  129. </button>
  130. </div>
  131. </div>
  132. <div class="setting-item">
  133. <span class="label">打印</span>
  134. <div class="options">
  135. <button
  136. v-for="(mode, index) in printModes"
  137. :key="index"
  138. :class="{ active: settings.autoPrint === mode.value }"
  139. @click="updateSetting('autoPrint', mode.value)"
  140. >
  141. {{ mode.label }}
  142. </button>
  143. </div>
  144. </div>
  145. <div class="setting-item">
  146. <span class="label">自动登出</span>
  147. <div class="options">
  148. <button
  149. :class="{
  150. active: settings.autoLogout,
  151. }"
  152. @click="updateSetting('autoLogout', true)"
  153. >
  154. 开启
  155. </button>
  156. <button
  157. :class="{
  158. active: !settings.autoLogout,
  159. }"
  160. @click="updateSetting('autoLogout', false)"
  161. >
  162. 关闭
  163. </button>
  164. </div>
  165. </div>
  166. <div v-if="settings.autoLogout" class="setting-item">
  167. <span class="label">登出时间</span>
  168. <div class="options">
  169. <button
  170. v-for="time in logoutTimes"
  171. :key="time.value"
  172. :class="{ active: settings.autoLogoutTimeout === time.value }"
  173. @click="updateSetting('autoLogoutTimeout', time.value)"
  174. >
  175. {{ time.label }}
  176. </button>
  177. </div>
  178. </div>
  179. </div>
  180. <!-- 键盘 -->
  181. <transition name="slide-up">
  182. <div class="keyboard" v-if="keyboardVisible">
  183. <SimpleKeyboard
  184. :input="currentInputValue"
  185. :layout
  186. @onChange="handleKeyboardInput"
  187. @onKeyPress="handleKeyPress"
  188. />
  189. </div>
  190. </transition>
  191. </template>
  192. <script setup lang="ts">
  193. import { ref, computed, onMounted, onUnmounted } from 'vue'
  194. import { format } from 'date-fns'
  195. import {
  196. getSystemSettings,
  197. setLanguage,
  198. setAutoPrint,
  199. setAutoLogout,
  200. getTemperatureRange,
  201. setDHCP,
  202. setLocalIP,
  203. setAutoLogoutTime,
  204. setAllTemperature,
  205. setIncubateBoxTemperature,
  206. setPlateBoxTemperature,
  207. setDateAndTime,
  208. } from '@/services'
  209. import { eMessage, isValidIPv4 } from '../utils'
  210. import { useDeviceStore } from '@/store/index'
  211. const deviceStore = useDeviceStore()
  212. const layout = ref()
  213. const numericLayout = {
  214. default: [
  215. '1 2 3',
  216. '4 5 6',
  217. '7 8 9',
  218. '{bksp} 0 {enter}', // 包含删除和确认键
  219. ],
  220. }
  221. // 系统设置状态
  222. interface Settings {
  223. language: string
  224. autoPrint: boolean
  225. autoLogout: boolean
  226. autoLogoutTimeout: number
  227. allTemperature: number | undefined
  228. incubateBoxTemperature: number
  229. plateBoxTemperature: number
  230. DHCP: boolean
  231. localIp: string
  232. }
  233. const settings = ref<Settings>({
  234. language: 'zh-CN',
  235. autoPrint: true,
  236. autoLogout: false,
  237. autoLogoutTimeout: 10,
  238. allTemperature: 20,
  239. incubateBoxTemperature: 20,
  240. plateBoxTemperature: 20,
  241. DHCP: true,
  242. localIp: '',
  243. })
  244. // 日期和时间
  245. const currentDate = ref(new Date())
  246. const time = ref('00:00:00')
  247. let pickerKey = ref(0)
  248. // const dateFormats = ['MM.dd.yyyy', 'dd.MM.yyyy', 'yyyy.MM.dd']
  249. // const selectedDateFormat = ref(dateFormats[2])
  250. // 选项配置
  251. const languages = [
  252. { label: 'English', value: 'en_US' },
  253. { label: '简体中文', value: 'zh_CN' },
  254. ]
  255. interface PrintMode {
  256. label: string
  257. value: boolean
  258. }
  259. const printModes: PrintMode[] = [
  260. { label: '自动', value: true as const },
  261. { label: '手动', value: false as const },
  262. ]
  263. const logoutTimes = [
  264. { label: '10分钟', value: 10 },
  265. { label: '30分钟', value: 30 },
  266. { label: '1小时', value: 60 },
  267. { label: '2小时', value: 120 },
  268. ]
  269. // 格式化日期显示
  270. const formattedDate = computed(() =>
  271. format(currentDate.value, 'yyyy.MM.dd HH:mm:ss'),
  272. )
  273. // 获取系统设置
  274. const fetchSettings = async () => {
  275. try {
  276. const res = await getSystemSettings()
  277. if (res.success) {
  278. settings.value = res.data
  279. if (res.data.incubateBoxTemperature === res.data.plateBoxTemperature) {
  280. settings.value.allTemperature = res.data.incubateBoxTemperature
  281. }
  282. }
  283. } catch (error) {
  284. eMessage.error('获取系统设置失败')
  285. }
  286. }
  287. function isIntegerInRange(str: string): boolean {
  288. // 匹配 20-40 的整数
  289. const regex = /^(2[0-9]|3[0-9]|40)$/
  290. return regex.test(str)
  291. }
  292. // 更新设置
  293. const updateSetting = async (key: string, value: any) => {
  294. try {
  295. let res
  296. switch (key) {
  297. case 'date':
  298. // 暂停定时器
  299. clearInterval(intervalId.value)
  300. res = await setDateAndTime(value)
  301. //更新后利用KEY重新加载date-picker,不然focus不起作用
  302. pickerKey.value = new Date().getTime()
  303. // 恢复定时器
  304. startTimer()
  305. break
  306. case 'language':
  307. res = await setLanguage(value)
  308. break
  309. case 'autoPrint':
  310. res = await setAutoPrint(value)
  311. break
  312. case 'autoLogout':
  313. res = await setAutoLogout(value)
  314. break
  315. case 'autoLogoutTimeout':
  316. res = await setAutoLogoutTime(value)
  317. break
  318. case 'allTemperature':
  319. if (!isIntegerInRange(value)) {
  320. eMessage.error('输入有误, 温度范围为20℃-40℃')
  321. return
  322. }
  323. res = await setAllTemperature(value)
  324. break
  325. case 'incubateBoxTemperature':
  326. if (!isIntegerInRange(value)) {
  327. eMessage.error('输入有误, 温度范围为20℃-40℃')
  328. return
  329. }
  330. res = await setIncubateBoxTemperature(value)
  331. break
  332. case 'plateBoxTemperature':
  333. if (!isIntegerInRange(value)) {
  334. eMessage.error('输入有误, 温度范围为20℃-40℃')
  335. return
  336. }
  337. res = await setPlateBoxTemperature(value)
  338. break
  339. case 'DHCP':
  340. res = await setDHCP(value)
  341. break
  342. case 'localIP':
  343. const addr = value.trim()
  344. if (!isValidIPv4(addr)) {
  345. eMessage.error('请输入合法的IP地址')
  346. return
  347. }
  348. hideKeyboard()
  349. res = await setLocalIP(addr)
  350. break
  351. }
  352. if (res?.success) {
  353. settings.value = { ...settings.value, [key]: value }
  354. if (key === 'allTemperature') {
  355. settings.value.incubateBoxTemperature =
  356. settings.value.plateBoxTemperature = value
  357. }
  358. if (
  359. settings.value.incubateBoxTemperature ===
  360. settings.value.plateBoxTemperature
  361. ) {
  362. settings.value.allTemperature = settings.value.incubateBoxTemperature
  363. } else {
  364. settings.value.allTemperature = undefined
  365. }
  366. }
  367. } catch (error) {
  368. console.log(error)
  369. eMessage.error('设置更新失败')
  370. }
  371. }
  372. // 定时器相关
  373. const intervalId = ref<number | null>(null)
  374. // 启动定时器
  375. const startTimer = () => {
  376. intervalId.value = setInterval(() => {
  377. const now = new Date()
  378. currentDate.value = now
  379. time.value = format(now, 'yyyy.MM.dd HH:mm:ss')
  380. }, 1000)
  381. }
  382. const temperatures = ref<number[]>([])
  383. // 初始化
  384. onMounted(async () => {
  385. await fetchSettings()
  386. // 启动定时器
  387. startTimer()
  388. const res = await getTemperatureRange()
  389. if (res && res.success) {
  390. temperatures.value = res.data
  391. } else {
  392. res && res.data && res.data.info && eMessage.error(res.data.info)
  393. }
  394. })
  395. // 在组件卸载时清理定时器
  396. onUnmounted(() => {
  397. if (intervalId.value) {
  398. clearInterval(intervalId.value)
  399. }
  400. hideKeyboard()
  401. })
  402. // 键盘相关状态
  403. const keyboardVisible = ref(false)
  404. const currentInputValue = ref('')
  405. // 显示键盘
  406. const showKeyboard = (KeyboardType: string | undefined, type: number) => {
  407. inputNumberType.value = type
  408. if (KeyboardType) {
  409. layout.value = numericLayout
  410. } else {
  411. layout.value = undefined
  412. }
  413. keyboardVisible.value = false
  414. setTimeout(() => {
  415. keyboardVisible.value = true
  416. }, 200)
  417. // 清空当前输入值,避免累加
  418. currentInputValue.value = settings.value[inputType[type]]
  419. }
  420. const inputNumberType = ref(0)
  421. const inputType = {
  422. 1: 'allTemperature',
  423. 2: 'incubateBoxTemperature',
  424. 3: 'plateBoxTemperature',
  425. 4: 'localIp',
  426. }
  427. // 处理键盘输入
  428. const handleKeyboardInput = (value: string) => {
  429. // 更新当前输入值
  430. currentInputValue.value = value
  431. settings.value[inputType[inputNumberType.value]] = value
  432. }
  433. // 处理键盘按键
  434. const handleKeyPress = (button: string) => {
  435. if (button === '{enter}') {
  436. hideKeyboard()
  437. } else if (button === '{bksp}') {
  438. // 处理退格键
  439. const value = currentInputValue.value
  440. if (value.length > 0) {
  441. const newValue = value.slice(0, -1)
  442. handleKeyboardInput(newValue)
  443. }
  444. }
  445. }
  446. // 隐藏键盘
  447. const hideKeyboard = () => {
  448. keyboardVisible.value = false
  449. currentInputValue.value = ''
  450. }
  451. // 在组件卸载时清理状态
  452. onUnmounted(() => {
  453. hideKeyboard()
  454. })
  455. // 暂停定时器
  456. const pauseTimer = () => {
  457. if (intervalId.value) {
  458. console.log(111)
  459. clearInterval(intervalId.value)
  460. intervalId.value = null
  461. }
  462. }
  463. // 恢复定时器
  464. const resumeTimer = () => {
  465. if (!intervalId.value) {
  466. startTimer()
  467. }
  468. }
  469. </script>
  470. <style scoped lang="less">
  471. .device-management {
  472. width: 100%;
  473. height: 91.5vh;
  474. padding: 20px;
  475. box-sizing: border-box;
  476. background-color: #f5f7fa;
  477. display: flex;
  478. flex-direction: column;
  479. gap: 20px;
  480. .setting-item-box > :nth-child(2) {
  481. border-top: 1px solid #ddd;
  482. border-bottom: 1px solid #ddd;
  483. }
  484. .setting-item-no-border {
  485. border-radius: 0 !important;
  486. box-shadow: none !important;
  487. }
  488. .setting-item {
  489. background-color: #fff;
  490. border-radius: 12px;
  491. padding: 24px 32px;
  492. display: flex;
  493. align-items: center;
  494. justify-content: space-between;
  495. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
  496. transition: all 0.3s ease;
  497. .label {
  498. font-size: 28px;
  499. font-weight: 500;
  500. color: #303133;
  501. display: flex;
  502. align-items: center;
  503. .option-tag {
  504. font-size: 25px;
  505. padding: 20px 10px;
  506. margin: 0 10px;
  507. }
  508. }
  509. .value {
  510. font-size: 28px;
  511. color: #606266;
  512. }
  513. input {
  514. padding: 8px;
  515. border: 1px solid #ccc;
  516. border-radius: 4px;
  517. font-size: 30px;
  518. transition: box-shadow 0.2s ease;
  519. border-radius: 10px;
  520. }
  521. .options {
  522. display: flex;
  523. gap: 12px;
  524. flex-wrap: wrap;
  525. button {
  526. min-width: 120px;
  527. height: 56px;
  528. padding: 0 24px;
  529. border: 1px solid #dcdfe6;
  530. background-color: #fff;
  531. border-radius: 8px;
  532. font-size: 24px;
  533. color: #606266;
  534. cursor: pointer;
  535. transition: all 0.3s ease;
  536. display: flex;
  537. align-items: center;
  538. justify-content: center;
  539. &.active {
  540. color: #fff;
  541. background-color: #409eff;
  542. border-color: #409eff;
  543. box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
  544. }
  545. }
  546. }
  547. }
  548. }
  549. .keyboard {
  550. position: fixed;
  551. bottom: 0;
  552. left: 0;
  553. width: 100%;
  554. height: 300px;
  555. background-color: #f5f7fa;
  556. border-top-left-radius: 16px;
  557. border-top-right-radius: 16px;
  558. box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.1);
  559. z-index: 1000;
  560. }
  561. // 键盘动画
  562. .slide-up-enter-active,
  563. .slide-up-leave-active {
  564. transition: transform 0.3s ease;
  565. }
  566. .slide-up-enter-from,
  567. .slide-up-leave-to {
  568. transform: translateY(100%);
  569. }
  570. :deep(.date-input) {
  571. width: 250px;
  572. margin-right: 30px;
  573. .el-input__wrapper {
  574. height: 40px;
  575. }
  576. .el-input__inner {
  577. font-size: 18px;
  578. }
  579. }
  580. </style>