24 changed files with 350 additions and 361 deletions
-
1eslint.config.js
-
8src/apis/solution.ts
-
4src/apis/system.ts
-
2src/apis/user.ts
-
35src/components/common/FTImage/index.vue
-
2src/components/common/FTTable/index.vue
-
68src/components/container/Item/index.vue
-
40src/components/craft/AddCraft/index.vue
-
283src/components/home/AddLiquid/index.vue
-
29src/components/system/EditDate/index.vue
-
35src/components/user/Edit/index.vue
-
64src/hooks/useServerTime.ts
-
19src/layouts/default.vue
-
12src/libs/socket.ts
-
3src/libs/utils.ts
-
2src/router/routes.ts
-
12src/stores/systemStore.ts
-
12src/types/container.d.ts
-
4src/types/system.d.ts
-
3src/types/user.d.ts
-
4src/views/container/index.vue
-
21src/views/debug/index.vue
-
46src/views/home/index.vue
-
2src/views/user/index.vue
@ -1,9 +1,9 @@ |
|||
import http from 'libs/http' |
|||
|
|||
export const getSolsList = (params: System.Page = { pageNum: 1, pageSize: 999 }): Promise<System.PageResponse<Solution.SolutionItem>> => http.get(`/sols/list`, { params }) |
|||
export const getSolsList = (params: System.Page = { pageNum: 1, pageSize: 999 }): Promise<System.PageResponse<Solution.SolutionItem>> => http.post(`/solutions/list`, params) |
|||
|
|||
export const saveSols = (params: { name: string }): Promise<null> => http.post(`/sols`, params) |
|||
export const saveSols = (params: { name: string }): Promise<null> => http.post(`/solutions`, params) |
|||
|
|||
export const editSols = (params: { id: number, name: string }): Promise<null> => http.put(`/sols`, params) |
|||
export const editSols = (params: { id: number, name: string }): Promise<null> => http.put(`/solutions`, params) |
|||
|
|||
export const delSols = (ids: string): Promise<null> => http.delete(`/sols/${ids}`) |
|||
export const delSols = (ids: string): Promise<null> => http.delete(`/solutions/${ids}`) |
@ -0,0 +1,35 @@ |
|||
<script setup lang="ts"> |
|||
import { Knova } from 'knova' |
|||
|
|||
// 定义 knova 实例 |
|||
const knovaInstance = ref<Knova | null>(null) |
|||
|
|||
const images = ref([ |
|||
'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg', |
|||
'https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg', |
|||
]) |
|||
|
|||
onMounted(() => { |
|||
const container = document.getElementById('knova-container') |
|||
if (container) { |
|||
// 初始化 knova 实例 |
|||
knovaInstance.value = new Knova(container, { |
|||
items: images.value.map(src => ({ |
|||
src, |
|||
type: 'image', |
|||
})), |
|||
gestures: true, // 启用手势操作 |
|||
zoom: true, // 启用缩放功能 |
|||
doubleTap: true, // 启用双击缩放 |
|||
}) |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
<template lang="pug"> |
|||
div#knova-container |
|||
</template> |
|||
|
|||
<style scoped lang="scss"> |
|||
|
|||
</style> |
@ -1,202 +1,101 @@ |
|||
<script setup lang="ts"> |
|||
import { getContainerList } from 'apis/container' |
|||
import { getSolsList } from 'apis/solution' |
|||
import { socket } from 'libs/socket' |
|||
import { useHomeStore } from 'stores/homeStore' |
|||
import { useSystemStore } from 'stores/systemStore' |
|||
import { onMounted, onUnmounted, ref } from 'vue' |
|||
|
|||
const emits = defineEmits(['ok', 'cancel']) |
|||
|
|||
const homeStore = useHomeStore() |
|||
const systemStore = useSystemStore() |
|||
|
|||
onMounted(async () => { |
|||
await getSols() |
|||
await queryContainerList() |
|||
socket.init(receiveMessage, 'cmd_debug') |
|||
socket.init(receiveMessage, 'cmd_response') |
|||
defineProps({ |
|||
visible: { |
|||
type: Boolean, |
|||
default: false, |
|||
}, |
|||
}) |
|||
const emits = defineEmits(['close']) |
|||
|
|||
onUnmounted(() => { |
|||
socket.unregisterCallback(receiveMessage, 'cmd_debug') |
|||
socket.unregisterCallback(receiveMessage, 'cmd_response') |
|||
}) |
|||
|
|||
const queryContainerList = async () => { |
|||
containerList.value = await getContainerList() |
|||
} |
|||
|
|||
let currentCommandId = '' |
|||
const receiveMessage = (data: Socket.cmdData) => { |
|||
data.commandId === currentCommandId && systemStore.pushSystemList(data) |
|||
} |
|||
|
|||
const form = ref<{ |
|||
columns?: number[] |
|||
containerId?: number |
|||
volume?: number |
|||
}>({}) |
|||
const formRef = ref() |
|||
|
|||
const validateHandle = (rule: any, value: any, callback: any) => { |
|||
if (!value?.length) { |
|||
callback(new Error('请选择试管')) |
|||
} |
|||
else { |
|||
callback() |
|||
} |
|||
} |
|||
|
|||
const rules = { |
|||
columns: [ |
|||
{ required: true, message: '请选择试管', trigger: 'change', validator: validateHandle }, |
|||
], |
|||
containerId: [ |
|||
{ required: true, message: '请选择溶液', trigger: 'change' }, |
|||
], |
|||
volume: [ |
|||
{ required: true, message: '请输入容量', trigger: 'blur' }, |
|||
], |
|||
} |
|||
const activeTab = ref(1) |
|||
|
|||
const okHandle = async () => { |
|||
try { |
|||
const valid = await formRef.value.validate() |
|||
if (!valid) { |
|||
return |
|||
} |
|||
currentCommandId = Date.now().toString() |
|||
const params = { |
|||
commandId: currentCommandId, |
|||
command: 'liquid_add', |
|||
params: form.value, |
|||
} |
|||
await homeStore.sendControl(params) |
|||
emits('ok') |
|||
} |
|||
catch (error) { |
|||
console.log(error) |
|||
} |
|||
} |
|||
const cancel = () => { |
|||
emits('cancel') |
|||
} |
|||
|
|||
const solsList = ref<Solution.SolutionItem[]>([]) |
|||
const containerList = ref<Container.ContainerItem[]>([]) |
|||
|
|||
const getSols = async () => { |
|||
const res = await getSolsList() |
|||
solsList.value = res.list |
|||
} |
|||
// |
|||
// const tubes = computed(() => { |
|||
// const tray = systemStore.systemStatus.trays?.find(item => item.inSolutionPositon) |
|||
// return tray?.tubes || [] |
|||
// }) |
|||
|
|||
const selectedColumns = ref(Array.from({ length: 5 }).fill(false)) |
|||
|
|||
const mousedownHandle = async (index: number) => { |
|||
// if (!tubes.value.find(item => item.columnNum === index)?.exists) { |
|||
// FtMessage.error('该列没有试管') |
|||
// return |
|||
// } |
|||
selectedColumns.value[index - 1] = !selectedColumns.value[index - 1] |
|||
form.value.columns = selectedColumns.value.map((item, index) => { |
|||
return item ? index + 1 : false |
|||
}).filter(item => item !== false) |
|||
formRef.value.validateField('columns') |
|||
const close = () => { |
|||
emits('close') |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<FtDialog visible title="添加溶液" width="40%" :ok-handle="okHandle" @cancel="cancel"> |
|||
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto"> |
|||
<el-form-item label="选择试管" prop="columns"> |
|||
<div class="tube-item"> |
|||
<div |
|||
v-for="item in 5" |
|||
:key="item" |
|||
class="tube-line" |
|||
:class="{ 'tube-line-active': selectedColumns[item - 1] }" |
|||
|
|||
@click.prevent="() => mousedownHandle(item)" |
|||
@touch.prevent="() => mousedownHandle(item)" |
|||
> |
|||
<span v-for="i in 8" :key="i" class="tube-line-inner" /> |
|||
</div> |
|||
</div> |
|||
</el-form-item> |
|||
<el-form-item label="选择溶液" prop="solutionId"> |
|||
<el-select v-model="form.containerId" placeholder="请选择溶液"> |
|||
<el-option v-for="item in containerList.filter(i => i.type === 0)" :key="item.id" :label="solsList.find(s => s.id === item.solutionId)?.name" :value="item.id" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="容量" prop="volume"> |
|||
<el-input v-model="form.volume" type="number" placeholder="请输入容量"> |
|||
<template #append> |
|||
mL |
|||
</template> |
|||
</el-input> |
|||
</el-form-item> |
|||
</el-form> |
|||
</FtDialog> |
|||
<template lang="pug"> |
|||
div.dialog-box(v-if="visible") |
|||
div.title |
|||
p(:class="{ 'tab-active': activeTab === 1 }", @click="activeTab = 1") |
|||
| 滴定位1 |
|||
p(:class="{ 'tab-active': activeTab === 2 }", @click="activeTab = 2") |
|||
| 滴定位2 |
|||
el-icon.close-icon(@click="close") |
|||
Close |
|||
div.body |
|||
div.button-box |
|||
ft-button |
|||
| 移至加热 |
|||
ft-button |
|||
| 移至托盘 |
|||
ft-button |
|||
| 加入磁子 |
|||
div.form-box |
|||
span |
|||
| 选择溶液 |
|||
el-select |
|||
el-input |
|||
template(#append) |
|||
el-select |
|||
ft-button |
|||
| 添加 |
|||
div.form-box |
|||
span |
|||
| 搅拌设置 |
|||
el-input |
|||
template(#append) |
|||
| ms |
|||
el-input |
|||
template(#append) |
|||
| s |
|||
ft-button |
|||
| 开始 |
|||
</template> |
|||
|
|||
<style scoped lang="scss"> |
|||
.el-tag { |
|||
margin-right: 5px; |
|||
} |
|||
.el-row { |
|||
height: 450px; |
|||
.el-col { |
|||
height: 100%; |
|||
overflow: auto; |
|||
:deep(.el-tag) { |
|||
width: 100%; |
|||
margin-bottom: 5px; |
|||
.el-tag__content { |
|||
display: flex; |
|||
width: 100%; |
|||
justify-content: space-between; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
.tube-item { |
|||
padding: 5px; |
|||
background: #384D5D; |
|||
border-radius: 10px; |
|||
display: grid; |
|||
grid-template-columns: repeat(5, 1fr); |
|||
grid-template-rows: repeat(1, 1fr); |
|||
grid-gap: 5px; |
|||
position: relative; |
|||
.tube-line { |
|||
display: flex; |
|||
flex-direction: column; |
|||
.tube-line-inner { |
|||
display: inline-block; |
|||
width: 25px; |
|||
height: 25px; |
|||
border-radius: 50%; |
|||
background: #fff; |
|||
margin: 2px; |
|||
transition: background 0.5s; |
|||
} |
|||
} |
|||
.tube-line-disable { |
|||
.tube-line-inner { |
|||
background: #C6C6C6; |
|||
} |
|||
} |
|||
.tube-line-active { |
|||
.tube-line-inner { |
|||
background: #26D574; |
|||
} |
|||
} |
|||
|
|||
} |
|||
<style scoped lang="stylus"> |
|||
.dialog-box |
|||
position absolute |
|||
width 500px |
|||
bottom 0 |
|||
right 250px |
|||
box-shadow var(--el-box-shadow-light) |
|||
background #fff |
|||
.title |
|||
background #F5F7FA |
|||
height 30px |
|||
display flex |
|||
color #818181 |
|||
position relative |
|||
.close-icon |
|||
font-size 20px |
|||
position absolute |
|||
right 10px |
|||
top 5px |
|||
.tab-active |
|||
color #12C290 |
|||
background #fff |
|||
p |
|||
height 100% |
|||
line-height 30px |
|||
text-align center |
|||
width 100px |
|||
.body |
|||
padding 10px 10px 0 |
|||
.button-box |
|||
width 100% |
|||
display grid |
|||
grid-template-columns repeat(3, 1fr) |
|||
gap 20px |
|||
.form-box |
|||
padding 5px |
|||
background #F5F9FD |
|||
display grid |
|||
grid-template-columns 1fr 2fr 2fr 1.5fr |
|||
gap 10px |
|||
align-items center |
|||
margin 10px 0 |
|||
:deep(.el-input-group__append) |
|||
width 80px |
|||
padding 0 |
|||
</style> |
@ -1,44 +1,64 @@ |
|||
import { getTime } from 'apis/system' |
|||
import { getTime, setTime } from 'apis/system' |
|||
import { formatDateTime } from 'libs/utils' |
|||
import { onMounted, onUnmounted, ref } from 'vue' |
|||
|
|||
export function useServerTime() { |
|||
const serverTime = ref<number | null>(null) // 初始服务器时间戳
|
|||
const serverTime = ref<number | undefined>() // 初始服务器时间戳
|
|||
const clientFetchTime = ref<number | null>(null) // 获取服务器时间时的客户端时间戳
|
|||
const currentTime = ref<string>('0000-00-00 00:00:00') |
|||
const currentTime = ref<string>('0001-00-00 00:00:00') |
|||
const editVisible = ref(false) |
|||
|
|||
let interval: number | null = null |
|||
|
|||
onMounted(async () => { |
|||
try { |
|||
serverTime.value = await getTime() |
|||
currentTime.value = formatDateTime(undefined, new Date(serverTime.value)) |
|||
clientFetchTime.value = Date.now() |
|||
let num = 0 |
|||
interval = window.setInterval(async () => { |
|||
if (num > 60) { |
|||
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) |
|||
await getDateTime() |
|||
} |
|||
// eslint-disable-next-line unused-imports/no-unused-vars
|
|||
|
|||
catch (error) { |
|||
// currentTime.value = formatDateTime(undefined, new Date())
|
|||
console.error('获取服务器时间失败') |
|||
} |
|||
}) |
|||
|
|||
const openDialog = () => { |
|||
editVisible.value = true |
|||
} |
|||
|
|||
const closeDialog = () => { |
|||
editVisible.value = false |
|||
} |
|||
|
|||
const getDateTime = async () => { |
|||
serverTime.value = (await getTime())?.epochMilli |
|||
clientFetchTime.value = Date.now() |
|||
let num = 0 |
|||
if (interval) |
|||
clearInterval(interval) |
|||
interval = window.setInterval(async () => { |
|||
if (num > 60) { |
|||
serverTime.value = (await getTime())?.epochMilli |
|||
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) |
|||
} |
|||
|
|||
const setDateTime = async (time: number) => { |
|||
await setTime({ epochMilli: time }) |
|||
await getDateTime() |
|||
} |
|||
|
|||
onUnmounted(() => { |
|||
if (interval) |
|||
clearInterval(interval) |
|||
}) |
|||
|
|||
return { currentTime } |
|||
return { currentTime, serverTime, editVisible, setDateTime, openDialog, closeDialog } |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue