Browse Source

fix: 倒计时和时间已服务器为准

master
guoapeng 2 months ago
parent
commit
a228697c2b
  1. 1
      src/apis/system.ts
  2. 105
      src/components/common/Countdown.vue
  3. 24
      src/components/home/Tube/index.vue
  4. 43
      src/hooks/useServerTime.ts
  5. 15
      src/layouts/default.vue
  6. 4
      src/libs/utils.ts
  7. 5
      src/stores/systemStore.ts
  8. 1
      src/types/system.d.ts
  9. 127
      src/views/debug/index.vue

1
src/apis/system.ts

@ -5,3 +5,4 @@ export const control = <T>(params: System.CmdControlParams<T>): Promise<null> =>
export const getStatus = (): Promise<System.SystemStatus> => http.get('/sys/device-status') export const getStatus = (): Promise<System.SystemStatus> => http.get('/sys/device-status')
export const getPoint = (): Promise<string> => http.get('/cmd/step/gantry-point') export const getPoint = (): Promise<string> => http.get('/cmd/step/gantry-point')
export const requireOutTray = (): Promise<{ moduleCode: string }[]> => http.get('/self-test/require-out-tray') export const requireOutTray = (): Promise<{ moduleCode: string }[]> => http.get('/self-test/require-out-tray')
export const getTime = (): Promise<number> => http.get('/sys/datetime')

105
src/components/common/Countdown.vue

@ -0,0 +1,105 @@
<script setup lang="ts">
import { useSystemStore } from 'stores/systemStore'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
const props = defineProps<{
current: number
startTime: number
duration: number //
}>()
const systemStore = useSystemStore()
const remaining = ref(0)
let timer: number | null = null
const calculateRemaining = () => {
// const now = new Date()
// //
// const timeDiff = now.getTime() - props.current
//
console.log(props)
const endTime = props.startTime + props.duration * 1000
// = - -
remaining.value = Math.max(0, endTime - new Date(systemStore.currentTime).getTime())
console.log('remaining', remaining.value)
}
const updateCountdown = () => {
if (remaining.value <= 0) {
stopTimer()
return
}
remaining.value -= 1000
}
const startTimer = () => {
updateCountdown() //
timer = window.setInterval(() => {
calculateRemaining()
updateCountdown()
}, 1000)
}
const stopTimer = () => {
if (timer) {
console.log(111)
clearInterval(timer)
timer = null
}
}
onMounted(() => {
startTimer()
})
onBeforeUnmount(() => {
stopTimer()
})
//
const days = ref(0)
const hours = ref('00')
const minutes = ref('00')
const seconds = ref('00')
//
watch(
() => remaining.value,
(newVal) => {
if (newVal <= 0 || !newVal) {
days.value = 0
hours.value = '00'
minutes.value = '00'
seconds.value = '00'
return
}
days.value = Math.floor(newVal / (1000 * 60 * 60 * 24))
const hoursNum = Math.floor((newVal % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
hours.value = hoursNum.toString().padStart(2, '0')
const minutesNum = Math.floor((newVal % (1000 * 60 * 60)) / (1000 * 60))
minutes.value = minutesNum.toString().padStart(2, '0')
const secondsNum = Math.floor((newVal % (1000 * 60)) / 1000)
seconds.value = secondsNum.toString().padStart(2, '0')
},
)
</script>
<template>
<div class="countdown">
<span v-if="days">{{ days }}</span>
{{ hours }}:{{ minutes }}:{{ seconds }}
</div>
</template>
<style scoped>
.countdown {
text-align: center;
font-size: 12px;
color: #FF4500;
}
</style>

24
src/components/home/Tube/index.vue

@ -5,10 +5,10 @@ import errorIcon from 'assets/images/error.svg'
import ingIcon from 'assets/images/ing.svg' import ingIcon from 'assets/images/ing.svg'
import successIcon from 'assets/images/success.svg' import successIcon from 'assets/images/success.svg'
import waitIcon from 'assets/images/wait.svg' import waitIcon from 'assets/images/wait.svg'
import { socket } from 'libs/socket'
import CountDown from 'components/common/Countdown.vue'
import { useHomeStore } from 'stores/homeStore' import { useHomeStore } from 'stores/homeStore'
import { useSystemStore } from 'stores/systemStore' import { useSystemStore } from 'stores/systemStore'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { computed, ref } from 'vue'
const props = withDefaults(defineProps<{ data: System.HeatArea }>(), { const props = withDefaults(defineProps<{ data: System.HeatArea }>(), {
data: () => ({ data: () => ({
@ -17,6 +17,7 @@ const props = withDefaults(defineProps<{ data: System.HeatArea }>(), {
trayStatus: 0, trayStatus: 0,
fanOpen: false, fanOpen: false,
heating: false, heating: false,
heatingType: 'constant',
capExist: false, capExist: false,
temperature: 0, temperature: 0,
targetTemperature: 0, targetTemperature: 0,
@ -25,19 +26,6 @@ const props = withDefaults(defineProps<{ data: System.HeatArea }>(), {
const emits = defineEmits(['selectChange', 'setTemperature']) const emits = defineEmits(['selectChange', 'setTemperature'])
onMounted(() => {
socket.init(receiveMessage, 'heat_countdown')
})
onUnmounted(() => {
socket.unregisterCallback(receiveMessage, 'heat_countdown')
})
const numList = ref<{ heatModuleCode: string, countdownStr: string }[]>([])
const receiveMessage = (data: any) => {
numList.value = data
}
const homeStore = useHomeStore() const homeStore = useHomeStore()
const systemStore = useSystemStore() const systemStore = useSystemStore()
const mousedownHandle = async (e: Event) => { const mousedownHandle = async (e: Event) => {
@ -122,7 +110,7 @@ defineExpose({
<div class="tube-item" :class="{ 'tube-item-heat': ['warm_up', 'thermostatic'].includes(data.heatingType), 'tube-item-constant': data.heatingType === 'constant', 'tube-item-fan': data.fanOpen }" @click.prevent="mousedownHandle" @touch.prevent="mousedownHandle"> <div class="tube-item" :class="{ 'tube-item-heat': ['warm_up', 'thermostatic'].includes(data.heatingType), 'tube-item-constant': data.heatingType === 'constant', 'tube-item-fan': data.fanOpen }" @click.prevent="mousedownHandle" @touch.prevent="mousedownHandle">
<div v-if="data.trayStatus === 0" class="tube-disable" /> <div v-if="data.trayStatus === 0" class="tube-disable" />
<div <div
v-if="data.trayStatus !== 0 && craft?.state"
v-if="craft?.state"
class="status" :class="{ class="status" :class="{
'status-success': craft?.state === 'FINISHED', 'status-success': craft?.state === 'FINISHED',
'status-wait': craft?.state === 'READY', 'status-wait': craft?.state === 'READY',
@ -171,9 +159,7 @@ defineExpose({
<span v-show="data.heatingType === 'constant'" style="color: #eccbb6;font-weight: bold ">恒温中</span> <span v-show="data.heatingType === 'constant'" style="color: #eccbb6;font-weight: bold ">恒温中</span>
<span v-show="data.fanOpen" style="color: #6CD3FF;font-weight: bold ">降温中</span> <span v-show="data.fanOpen" style="color: #6CD3FF;font-weight: bold ">降温中</span>
</div> </div>
<div v-if="numList.find(item => item.heatModuleCode === data.moduleCode)?.countdownStr" style="text-align: center;font-size: 12px;color:#FE0A0A ">
{{ numList.find(item => item.heatModuleCode === data.moduleCode)?.countdownStr }}
</div>
<CountDown v-if="data.startHeatTime && data.targetTime" :current="new Date().getTime()" :start-time="data.startHeatTime" :duration="data.targetTime" />
<div class="footer"> <div class="footer">
<div class="tem-box"> <div class="tem-box">
<span :class="{ 'active-footer': hearInfo?.selected }"> {{ data.targetTemperature || '--' }}</span> <span :class="{ 'active-footer': hearInfo?.selected }"> {{ data.targetTemperature || '--' }}</span>

43
src/hooks/useServerTime.ts

@ -0,0 +1,43 @@
import { getTime } from 'apis/system'
import { formatDateTime } from 'libs/utils'
import { onMounted, onUnmounted, ref } from 'vue'
export function useServerTime() {
const serverTime = ref<number | null>(null) // 初始服务器时间戳
const clientFetchTime = ref<number | null>(null) // 获取服务器时间时的客户端时间戳
const currentTime = ref<string>('0000-00-00 00:00:00')
let interval: number | null = null
onMounted(async () => {
try {
serverTime.value = await getTime()
clientFetchTime.value = Date.now()
let num = 0
interval = window.setInterval(async () => {
if (num > 10) {
serverTime.value = await getTime()
clientFetchTime.value = Date.now()
num = 0
}
if (serverTime.value && clientFetchTime.value) {
const elapsed = Date.now() - clientFetchTime.value
const currentServerTime = serverTime.value + elapsed
currentTime.value = formatDateTime(undefined, new Date(currentServerTime))
num++
}
}, 1000)
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (error) {
console.error('获取服务器时间失败')
}
})
onUnmounted(() => {
if (interval)
clearInterval(interval)
})
return { currentTime }
}

15
src/layouts/default.vue

@ -5,8 +5,8 @@ import Check from 'components/check/index.vue'
import Liquid from 'components/home/Liquid/index.vue' import Liquid from 'components/home/Liquid/index.vue'
import Stop from 'components/Stop/index.vue' import Stop from 'components/Stop/index.vue'
import { useActivateDebug } from 'hooks/useActivateDebug' import { useActivateDebug } from 'hooks/useActivateDebug'
import { useServerTime } from 'hooks/useServerTime'
import { isClose } from 'libs/socket' import { isClose } from 'libs/socket'
import { formatDateTime } from 'libs/utils'
import { authRoutes } from 'router/routes' import { authRoutes } from 'router/routes'
import { useSystemStore } from 'stores/systemStore' import { useSystemStore } from 'stores/systemStore'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
@ -14,14 +14,13 @@ import { useRouter } from 'vue-router'
const { handleLogoClick } = useActivateDebug() const { handleLogoClick } = useActivateDebug()
const { currentTime } = useServerTime()
const systemStore = useSystemStore() const systemStore = useSystemStore()
const router = useRouter() const router = useRouter()
const currentTime = ref(formatDateTime())
const timeInterval = setInterval(() => {
currentTime.value = formatDateTime()
}, 1000)
watch(() => currentTime.value, () => {
systemStore.currentTime = currentTime.value
})
onMounted(async () => { onMounted(async () => {
if (!systemStore.systemStatus.selfTest && systemStore.systemStatus.currentUser?.username !== 'test') { if (!systemStore.systemStatus.selfTest && systemStore.systemStatus.currentUser?.username !== 'test') {
@ -30,7 +29,7 @@ onMounted(async () => {
}) })
onUnmounted(() => { onUnmounted(() => {
clearInterval(timeInterval)
// clearInterval(timeInterval)
}) })
// metaisDefault=true isDebug=true,debug // metaisDefault=true isDebug=true,debug
const menuList = computed(() => { const menuList = computed(() => {
@ -100,7 +99,7 @@ const statusMap = {
</el-dropdown> </el-dropdown>
<div class="time"> <div class="time">
{{ currentTime }}
{{ systemStore.currentTime }}
</div> </div>
<div class="user"> <div class="user">
<el-dropdown class="user-dropdown" trigger="click"> <el-dropdown class="user-dropdown" trigger="click">

4
src/libs/utils.ts

@ -55,6 +55,7 @@ export const cmdNameMap = {
shake_origin: '摇匀电机回原点', shake_origin: '摇匀电机回原点',
filled_solution_start: '开始预充', filled_solution_start: '开始预充',
filled_solution_stop: '停止预充', filled_solution_stop: '停止预充',
debug_show_smog: '展示烟雾',
} }
export const generateColors = (count: number): string[] => { export const generateColors = (count: number): string[] => {
@ -99,8 +100,7 @@ export function isNumber(value: any) {
return typeof value === 'number' && !Number.isNaN(value) return typeof value === 'number' && !Number.isNaN(value)
} }
export function formatDateTime(template: string = 'YYYY-MM-DD HH:mm:ss'): string {
const now = new Date()
export function formatDateTime(template: string = 'YYYY-MM-DD HH:mm:ss', now: Date = new Date()): string {
const tokens: Record<string, string> = { const tokens: Record<string, string> = {
YYYY: String(now.getFullYear()), YYYY: String(now.getFullYear()),
MM: String(now.getMonth() + 1).padStart(2, '0'), MM: String(now.getMonth() + 1).padStart(2, '0'),

5
src/stores/systemStore.ts

@ -90,6 +90,8 @@ export const useSystemStore = defineStore('system', {
capExist: false, capExist: false,
temperature: 0, temperature: 0,
targetTemperature: 0, targetTemperature: 0,
startHeatTime: 1749105000000,
targetTime: 1800,
}, },
{ {
moduleCode: 'heat_module_02', moduleCode: 'heat_module_02',
@ -114,7 +116,7 @@ export const useSystemStore = defineStore('system', {
{ {
moduleCode: 'heat_module_04', moduleCode: 'heat_module_04',
trayUp: 1, trayUp: 1,
trayStatus: 1,
trayStatus: 0,
heatingType: 'stop', heatingType: 'stop',
fanOpen: true, fanOpen: true,
capExist: false, capExist: false,
@ -230,6 +232,7 @@ export const useSystemStore = defineStore('system', {
streamVisible: false, streamVisible: false,
systemList: [], systemList: [],
systemLogList: [], systemLogList: [],
currentTime: '0000-00-00 00:00:00',
}), }),
actions: { actions: {
insertLog(log: System.SystemLog) { insertLog(log: System.SystemLog) {

1
src/types/system.d.ts

@ -9,6 +9,7 @@ declare namespace System {
menuExpand: boolean menuExpand: boolean
loginForm: LoginForm loginForm: LoginForm
systemLogList: SystemLog[] systemLogList: SystemLog[]
currentTime: string
} }
interface SystemLog { interface SystemLog {
cmdName: string cmdName: string

127
src/views/debug/index.vue

@ -471,24 +471,36 @@ const debug_door_stop = async () => {
} }
await debugStore.sendControl(params) await debugStore.sendControl(params)
} }
//
// const debug_move_tray_to_solution_area = async () => {
// currentCommandId = Date.now().toString()
// const params = {
// commandId: currentCommandId,
// command: 'debug_move_tray_to_solution_area',
// params: {
// heatId: debugStore.formData.heatArea.index,
// },
// }
// await debugStore.sendControl(params)
// }
//
// const debug_move_tray_to_heat_area = async () => {
// currentCommandId = Date.now().toString()
// const params = {
// commandId: currentCommandId,
// command: 'debug_move_tray_to_heat_area',
// params: {
// heatId: debugStore.formData.heatArea.index,
// },
// }
// await debugStore.sendControl(params)
// }
const debug_move_tray_to_solution_area = async () => {
currentCommandId = Date.now().toString()
const params = {
commandId: currentCommandId,
command: 'debug_move_tray_to_solution_area',
params: {
heatId: debugStore.formData.heatArea.index,
},
}
await debugStore.sendControl(params)
}
const debug_move_tray_to_heat_area = async () => {
const debug_show_smog = async () => {
currentCommandId = Date.now().toString() currentCommandId = Date.now().toString()
const params = { const params = {
commandId: currentCommandId, commandId: currentCommandId,
command: 'debug_move_tray_to_heat_area',
command: 'debug_show_smog',
params: { params: {
heatId: debugStore.formData.heatArea.index, heatId: debugStore.formData.heatArea.index,
}, },
@ -520,29 +532,29 @@ const debug_gantry_to_heat_area_tray = async () => {
await debugStore.sendControl(params) await debugStore.sendControl(params)
} }
const debug_cap_in_heat_area = async () => {
currentCommandId = Date.now().toString()
const params = {
commandId: currentCommandId,
command: 'debug_cap_in_heat_area',
params: {
heatId: debugStore.formData.heatArea.index,
},
}
await debugStore.sendControl(params)
}
const debug_cap_out_heat_area = async () => {
currentCommandId = Date.now().toString()
const params = {
commandId: currentCommandId,
command: 'debug_cap_out_heat_area',
params: {
heatId: debugStore.formData.heatArea.index,
},
}
await debugStore.sendControl(params)
}
// const debug_cap_in_heat_area = async () => {
// currentCommandId = Date.now().toString()
// const params = {
// commandId: currentCommandId,
// command: 'debug_cap_in_heat_area',
// params: {
// heatId: debugStore.formData.heatArea.index,
// },
// }
// await debugStore.sendControl(params)
// }
//
// const debug_cap_out_heat_area = async () => {
// currentCommandId = Date.now().toString()
// const params = {
// commandId: currentCommandId,
// command: 'debug_cap_out_heat_area',
// params: {
// heatId: debugStore.formData.heatArea.index,
// },
// }
// await debugStore.sendControl(params)
// }
const debug_cap_up_one = async () => { const debug_cap_up_one = async () => {
currentCommandId = Date.now().toString() currentCommandId = Date.now().toString()
@ -1093,26 +1105,31 @@ const savePositionVisible = ref(false)
</template> </template>
<el-divider>复合操作</el-divider> <el-divider>复合操作</el-divider>
<div class="card-box"> <div class="card-box">
<div style="display: grid; grid-template-columns: repeat(1, 1fr); grid-template-rows: repeat(3, 1fr); gap: 10px">
<ft-button size="small" type="primary" :click-handle="debug_show_smog">
展示烟雾
</ft-button>
<ft-button size="small" type="primary" :click-handle="debug_gantry_to_heat_area_cap">
机械臂移至拍子位
</ft-button>
<ft-button size="small" type="primary" :click-handle="debug_gantry_to_heat_area_tray">
机械臂移至托盘位
</ft-button>
</div>
<el-form> <el-form>
<el-form-item> <el-form-item>
<ft-button size="small" type="primary" :click-handle="debug_move_tray_to_solution_area">
加热托盘移至加液
</ft-button>
<ft-button size="small" type="primary" :click-handle="debug_cap_in_heat_area">
安装拍子
</ft-button>
<ft-button size="small" type="primary" :click-handle="debug_move_tray_to_heat_area">
加液托盘移至加热
</ft-button>
<ft-button size="small" type="primary" :click-handle="debug_cap_out_heat_area">
拆卸拍子
</ft-button>
<ft-button size="small" type="primary" :click-handle="debug_gantry_to_heat_area_cap">
机械臂移至拍子位
</ft-button>
<ft-button size="small" type="primary" :click-handle="debug_gantry_to_heat_area_tray">
机械臂移至托盘位
</ft-button>
<!-- <ft-button size="small" type="primary" :click-handle="debug_move_tray_to_solution_area"> -->
<!-- 加热托盘移至加液 -->
<!-- </ft-button> -->
<!-- <ft-button size="small" type="primary" :click-handle="debug_cap_in_heat_area"> -->
<!-- 安装拍子 -->
<!-- </ft-button> -->
<!-- <ft-button size="small" type="primary" :click-handle="debug_move_tray_to_heat_area"> -->
<!-- 加液托盘移至加热 -->
<!-- </ft-button> -->
<!-- <ft-button size="small" type="primary" :click-handle="debug_cap_out_heat_area"> -->
<!-- 拆卸拍子 -->
<!-- </ft-button> -->
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>

Loading…
Cancel
Save