forked from gzt/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
628 lines
15 KiB
<template>
|
|
<div class="device-management">
|
|
<div class="setting-item">
|
|
<span class="label">日期与时间</span>
|
|
<el-date-picker
|
|
class="date-input"
|
|
v-model="time"
|
|
:key="pickerKey"
|
|
type="datetime"
|
|
placeholder="修改日期与时间"
|
|
format="YYYY-MM-DD HH:mm:ss"
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
@change="(val) => updateSetting('date', val)"
|
|
@focus="pauseTimer"
|
|
@blur="resumeTimer"
|
|
/>
|
|
</div>
|
|
|
|
<!-- <div class="setting-item">
|
|
<span class="label">时间</span>
|
|
<span class="value">{{ time }}</span>
|
|
</div> -->
|
|
|
|
<div class="setting-item">
|
|
<span class="label">语言</span>
|
|
<div class="options">
|
|
<button
|
|
v-for="lang in languages"
|
|
:key="lang.value"
|
|
:class="{ active: settings.language === lang.value }"
|
|
@click="updateSetting('language', lang.value)"
|
|
:disabled="lang.value == 'ko_KR'"
|
|
>
|
|
{{ lang.label }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="setting-item-box">
|
|
<div class="setting-item setting-item-no-border">
|
|
<span class="label">整体温度控制</span>
|
|
<div class="options">
|
|
<input
|
|
style="width: 150px"
|
|
v-model="settings.allTemperature"
|
|
type="number"
|
|
@focus="showKeyboard('number', 1)"
|
|
readonly
|
|
/>
|
|
<button
|
|
@click="updateSetting('allTemperature', settings.allTemperature)"
|
|
>
|
|
设置
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="setting-item setting-item-no-border">
|
|
<span class="label">孵育盘温度 <el-tag type="primary" class="option-tag"> {{ deviceStore.sensorState?.incubateBoxTemperature }}℃</el-tag></span>
|
|
|
|
<div class="options">
|
|
|
|
<input
|
|
style="width: 150px"
|
|
v-model="settings.incubateBoxTemperature"
|
|
type="number"
|
|
@focus="showKeyboard('number', 2)"
|
|
readonly
|
|
/>
|
|
<button
|
|
@click="
|
|
updateSetting(
|
|
'incubateBoxTemperature',
|
|
settings.incubateBoxTemperature,
|
|
)
|
|
"
|
|
>
|
|
设置
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="setting-item setting-item-no-border">
|
|
<span class="label">板夹区温度 <el-tag type="primary" class="option-tag"> {{ deviceStore.sensorState?.pboxTemperature }}℃</el-tag></span>
|
|
<div class="options">
|
|
|
|
|
|
<input
|
|
style="width: 150px"
|
|
v-model="settings.plateBoxTemperature"
|
|
type="number"
|
|
@focus="showKeyboard('number', 3)"
|
|
readonly
|
|
/>
|
|
<button
|
|
@click="
|
|
updateSetting('plateBoxTemperature', settings.plateBoxTemperature)
|
|
"
|
|
>
|
|
设置
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-item" style="border-radius: 0">
|
|
<span class="label">DHCP</span>
|
|
<div class="options">
|
|
<button
|
|
:class="{
|
|
active: settings.DHCP,
|
|
}"
|
|
@click="updateSetting('DHCP', true)"
|
|
>
|
|
开启
|
|
</button>
|
|
<button
|
|
:class="{
|
|
active: !settings.DHCP,
|
|
}"
|
|
@click="updateSetting('DHCP', false)"
|
|
>
|
|
关闭
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div v-if="!settings.DHCP" class="setting-item">
|
|
<span class="label">Local IP</span>
|
|
<div class="options">
|
|
<input
|
|
style="min-width: 250px"
|
|
v-model="settings.localIp"
|
|
type="text"
|
|
@focus="showKeyboard(undefined, 4)"
|
|
readonly
|
|
/>
|
|
<button @click="updateSetting('localIP', settings.localIp)">
|
|
设置
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="setting-item">
|
|
<span class="label">打印</span>
|
|
<div class="options">
|
|
<button
|
|
v-for="(mode, index) in printModes"
|
|
:key="index"
|
|
:class="{ active: settings.autoPrint === mode.value }"
|
|
@click="updateSetting('autoPrint', mode.value)"
|
|
>
|
|
{{ mode.label }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="setting-item">
|
|
<span class="label">自动登出</span>
|
|
<div class="options">
|
|
<button
|
|
:class="{
|
|
active: settings.autoLogout,
|
|
}"
|
|
@click="updateSetting('autoLogout', true)"
|
|
>
|
|
开启
|
|
</button>
|
|
<button
|
|
:class="{
|
|
active: !settings.autoLogout,
|
|
}"
|
|
@click="updateSetting('autoLogout', false)"
|
|
>
|
|
关闭
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div v-if="settings.autoLogout" class="setting-item">
|
|
<span class="label">登出时间</span>
|
|
<div class="options">
|
|
<button
|
|
v-for="time in logoutTimes"
|
|
:key="time.value"
|
|
:class="{ active: settings.autoLogoutTimeout === time.value }"
|
|
@click="updateSetting('autoLogoutTimeout', time.value)"
|
|
>
|
|
{{ time.label }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- 键盘 -->
|
|
<transition name="slide-up">
|
|
<div class="keyboard" v-if="keyboardVisible">
|
|
<SimpleKeyboard
|
|
:input="currentInputValue"
|
|
:layout
|
|
@onChange="handleKeyboardInput"
|
|
@onKeyPress="handleKeyPress"
|
|
/>
|
|
</div>
|
|
</transition>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
import { format } from 'date-fns'
|
|
import {
|
|
getSystemSettings,
|
|
setLanguage,
|
|
setAutoPrint,
|
|
setAutoLogout,
|
|
getTemperatureRange,
|
|
setDHCP,
|
|
setLocalIP,
|
|
setAutoLogoutTime,
|
|
setAllTemperature,
|
|
setIncubateBoxTemperature,
|
|
setPlateBoxTemperature,
|
|
setDateAndTime,
|
|
} from '@/services'
|
|
import { eMessage, isValidIPv4 } from '../utils'
|
|
import { useDeviceStore } from '@/store/index'
|
|
const deviceStore = useDeviceStore()
|
|
|
|
const layout = ref()
|
|
|
|
const numericLayout = {
|
|
default: [
|
|
'1 2 3',
|
|
'4 5 6',
|
|
'7 8 9',
|
|
'{bksp} 0 {enter}', // 包含删除和确认键
|
|
],
|
|
}
|
|
|
|
// 系统设置状态
|
|
interface Settings {
|
|
language: string
|
|
autoPrint: boolean
|
|
autoLogout: boolean
|
|
autoLogoutTimeout: number
|
|
allTemperature: number | undefined
|
|
incubateBoxTemperature: number
|
|
plateBoxTemperature: number
|
|
DHCP: boolean
|
|
localIp: string
|
|
}
|
|
|
|
const settings = ref<Settings>({
|
|
language: 'zh-CN',
|
|
autoPrint: true,
|
|
autoLogout: false,
|
|
autoLogoutTimeout: 10,
|
|
allTemperature: 20,
|
|
incubateBoxTemperature: 20,
|
|
plateBoxTemperature: 20,
|
|
DHCP: true,
|
|
localIp: '',
|
|
})
|
|
|
|
// 日期和时间
|
|
const currentDate = ref(new Date())
|
|
const time = ref('00:00:00')
|
|
let pickerKey = ref(0)
|
|
// const dateFormats = ['MM.dd.yyyy', 'dd.MM.yyyy', 'yyyy.MM.dd']
|
|
// const selectedDateFormat = ref(dateFormats[2])
|
|
|
|
// 选项配置
|
|
const languages = [
|
|
{ label: 'English', value: 'en_US' },
|
|
{ label: '简体中文', value: 'zh_CN' },
|
|
]
|
|
|
|
interface PrintMode {
|
|
label: string
|
|
value: boolean
|
|
}
|
|
|
|
const printModes: PrintMode[] = [
|
|
{ label: '自动', value: true as const },
|
|
{ label: '手动', value: false as const },
|
|
]
|
|
|
|
const logoutTimes = [
|
|
{ label: '10分钟', value: 10 },
|
|
{ label: '30分钟', value: 30 },
|
|
{ label: '1小时', value: 60 },
|
|
{ label: '2小时', value: 120 },
|
|
]
|
|
|
|
// 格式化日期显示
|
|
const formattedDate = computed(() =>
|
|
format(currentDate.value, 'yyyy.MM.dd HH:mm:ss'),
|
|
)
|
|
|
|
// 获取系统设置
|
|
const fetchSettings = async () => {
|
|
try {
|
|
const res = await getSystemSettings()
|
|
if (res.success) {
|
|
settings.value = res.data
|
|
if (res.data.incubateBoxTemperature === res.data.plateBoxTemperature) {
|
|
settings.value.allTemperature = res.data.incubateBoxTemperature
|
|
}
|
|
}
|
|
} catch (error) {
|
|
eMessage.error('获取系统设置失败')
|
|
}
|
|
}
|
|
|
|
function isIntegerInRange(str: string): boolean {
|
|
// 匹配 20-40 的整数
|
|
const regex = /^(2[0-9]|3[0-9]|40)$/
|
|
return regex.test(str)
|
|
}
|
|
|
|
// 更新设置
|
|
const updateSetting = async (key: string, value: any) => {
|
|
try {
|
|
let res
|
|
switch (key) {
|
|
case 'date':
|
|
// 暂停定时器
|
|
clearInterval(intervalId.value)
|
|
res = await setDateAndTime(value)
|
|
//更新后利用KEY重新加载date-picker,不然focus不起作用
|
|
pickerKey.value = new Date().getTime()
|
|
// 恢复定时器
|
|
startTimer()
|
|
break
|
|
case 'language':
|
|
res = await setLanguage(value)
|
|
break
|
|
case 'autoPrint':
|
|
res = await setAutoPrint(value)
|
|
break
|
|
case 'autoLogout':
|
|
res = await setAutoLogout(value)
|
|
break
|
|
case 'autoLogoutTimeout':
|
|
res = await setAutoLogoutTime(value)
|
|
break
|
|
case 'allTemperature':
|
|
if (!isIntegerInRange(value)) {
|
|
eMessage.error('输入有误, 温度范围为20℃-40℃')
|
|
return
|
|
}
|
|
res = await setAllTemperature(value)
|
|
break
|
|
case 'incubateBoxTemperature':
|
|
if (!isIntegerInRange(value)) {
|
|
eMessage.error('输入有误, 温度范围为20℃-40℃')
|
|
return
|
|
}
|
|
res = await setIncubateBoxTemperature(value)
|
|
break
|
|
case 'plateBoxTemperature':
|
|
if (!isIntegerInRange(value)) {
|
|
eMessage.error('输入有误, 温度范围为20℃-40℃')
|
|
return
|
|
}
|
|
res = await setPlateBoxTemperature(value)
|
|
break
|
|
case 'DHCP':
|
|
res = await setDHCP(value)
|
|
break
|
|
case 'localIP':
|
|
const addr = value.trim()
|
|
if (!isValidIPv4(addr)) {
|
|
eMessage.error('请输入合法的IP地址')
|
|
return
|
|
}
|
|
hideKeyboard()
|
|
res = await setLocalIP(addr)
|
|
break
|
|
}
|
|
|
|
if (res?.success) {
|
|
settings.value = { ...settings.value, [key]: value }
|
|
|
|
if (key === 'allTemperature') {
|
|
settings.value.incubateBoxTemperature =
|
|
settings.value.plateBoxTemperature = value
|
|
}
|
|
if (
|
|
settings.value.incubateBoxTemperature ===
|
|
settings.value.plateBoxTemperature
|
|
) {
|
|
settings.value.allTemperature = settings.value.incubateBoxTemperature
|
|
} else {
|
|
settings.value.allTemperature = undefined
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log(error)
|
|
eMessage.error('设置更新失败')
|
|
}
|
|
}
|
|
|
|
// 定时器相关
|
|
const intervalId = ref<number | null>(null)
|
|
|
|
// 启动定时器
|
|
const startTimer = () => {
|
|
intervalId.value = setInterval(() => {
|
|
const now = new Date()
|
|
currentDate.value = now
|
|
time.value = format(now, 'yyyy.MM.dd HH:mm:ss')
|
|
}, 1000)
|
|
}
|
|
|
|
const temperatures = ref<number[]>([])
|
|
|
|
// 初始化
|
|
onMounted(async () => {
|
|
await fetchSettings()
|
|
|
|
// 启动定时器
|
|
startTimer()
|
|
|
|
const res = await getTemperatureRange()
|
|
if (res && res.success) {
|
|
temperatures.value = res.data
|
|
} else {
|
|
res && res.data && res.data.info && eMessage.error(res.data.info)
|
|
}
|
|
})
|
|
|
|
// 在组件卸载时清理定时器
|
|
onUnmounted(() => {
|
|
if (intervalId.value) {
|
|
clearInterval(intervalId.value)
|
|
}
|
|
hideKeyboard()
|
|
})
|
|
|
|
// 键盘相关状态
|
|
const keyboardVisible = ref(false)
|
|
const currentInputValue = ref('')
|
|
|
|
// 显示键盘
|
|
const showKeyboard = (KeyboardType: string | undefined, type: number) => {
|
|
inputNumberType.value = type
|
|
if (KeyboardType) {
|
|
layout.value = numericLayout
|
|
} else {
|
|
layout.value = undefined
|
|
}
|
|
keyboardVisible.value = false
|
|
setTimeout(() => {
|
|
keyboardVisible.value = true
|
|
}, 200)
|
|
// 清空当前输入值,避免累加
|
|
currentInputValue.value = settings.value[inputType[type]]
|
|
}
|
|
|
|
const inputNumberType = ref(0)
|
|
|
|
const inputType = {
|
|
1: 'allTemperature',
|
|
2: 'incubateBoxTemperature',
|
|
3: 'plateBoxTemperature',
|
|
4: 'localIp',
|
|
}
|
|
|
|
// 处理键盘输入
|
|
const handleKeyboardInput = (value: string) => {
|
|
// 更新当前输入值
|
|
currentInputValue.value = value
|
|
settings.value[inputType[inputNumberType.value]] = value
|
|
}
|
|
|
|
// 处理键盘按键
|
|
const handleKeyPress = (button: string) => {
|
|
if (button === '{enter}') {
|
|
hideKeyboard()
|
|
} else if (button === '{bksp}') {
|
|
// 处理退格键
|
|
const value = currentInputValue.value
|
|
if (value.length > 0) {
|
|
const newValue = value.slice(0, -1)
|
|
handleKeyboardInput(newValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 隐藏键盘
|
|
const hideKeyboard = () => {
|
|
keyboardVisible.value = false
|
|
currentInputValue.value = ''
|
|
}
|
|
|
|
// 在组件卸载时清理状态
|
|
onUnmounted(() => {
|
|
hideKeyboard()
|
|
})
|
|
|
|
// 暂停定时器
|
|
const pauseTimer = () => {
|
|
if (intervalId.value) {
|
|
console.log(111)
|
|
clearInterval(intervalId.value)
|
|
intervalId.value = null
|
|
}
|
|
}
|
|
|
|
// 恢复定时器
|
|
const resumeTimer = () => {
|
|
if (!intervalId.value) {
|
|
startTimer()
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
.device-management {
|
|
width: 100%;
|
|
height: 91.5vh;
|
|
padding: 20px;
|
|
box-sizing: border-box;
|
|
background-color: #f5f7fa;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
.setting-item-box > :nth-child(2) {
|
|
border-top: 1px solid #ddd;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
|
|
.setting-item-no-border {
|
|
border-radius: 0 !important;
|
|
box-shadow: none !important;
|
|
}
|
|
.setting-item {
|
|
background-color: #fff;
|
|
border-radius: 12px;
|
|
padding: 24px 32px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
|
transition: all 0.3s ease;
|
|
|
|
.label {
|
|
font-size: 28px;
|
|
font-weight: 500;
|
|
color: #303133;
|
|
display: flex;
|
|
align-items: center;
|
|
.option-tag {
|
|
font-size: 25px;
|
|
padding: 20px 10px;
|
|
margin: 0 10px;
|
|
}
|
|
}
|
|
|
|
.value {
|
|
font-size: 28px;
|
|
color: #606266;
|
|
}
|
|
input {
|
|
padding: 8px;
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
font-size: 30px;
|
|
transition: box-shadow 0.2s ease;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.options {
|
|
display: flex;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
|
|
button {
|
|
min-width: 120px;
|
|
height: 56px;
|
|
padding: 0 24px;
|
|
border: 1px solid #dcdfe6;
|
|
background-color: #fff;
|
|
border-radius: 8px;
|
|
font-size: 24px;
|
|
color: #606266;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
&.active {
|
|
color: #fff;
|
|
background-color: #409eff;
|
|
border-color: #409eff;
|
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.keyboard {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 300px;
|
|
background-color: #f5f7fa;
|
|
border-top-left-radius: 16px;
|
|
border-top-right-radius: 16px;
|
|
box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.1);
|
|
z-index: 1000;
|
|
}
|
|
// 键盘动画
|
|
.slide-up-enter-active,
|
|
.slide-up-leave-active {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
.slide-up-enter-from,
|
|
.slide-up-leave-to {
|
|
transform: translateY(100%);
|
|
}
|
|
:deep(.date-input) {
|
|
width: 250px;
|
|
margin-right: 30px;
|
|
.el-input__wrapper {
|
|
height: 40px;
|
|
}
|
|
.el-input__inner {
|
|
font-size: 18px;
|
|
}
|
|
}
|
|
|
|
</style>
|