LiLongLong 3 months ago
parent
commit
f6836f14e5
  1. 2
      .env.prod
  2. 4
      .env.test
  3. 2
      package.json
  4. 3
      src/apis/home.ts
  5. 1
      src/apis/system.ts
  6. 12
      src/app.vue
  7. 1
      src/assets/images/empty.svg
  8. 1
      src/assets/images/error.svg
  9. 1
      src/assets/images/ing.svg
  10. 1
      src/assets/images/success.svg
  11. 1
      src/assets/images/wait.svg
  12. 2
      src/components/common/FTDialog/index.vue
  13. 176
      src/components/home/AddLiquid/index.vue
  14. 9
      src/components/home/SelectCraft/index.vue
  15. 15
      src/components/home/SetTemperature/index.vue
  16. 2
      src/components/home/Stop/index.vue
  17. 123
      src/components/home/Tube/index.vue
  18. 25
      src/hooks/useActivateDebug.ts
  19. 11
      src/layouts/default.vue
  20. 4
      src/router/routes.ts
  21. 26
      src/stores/homeStore.ts
  22. 181
      src/stores/systemStore.ts
  23. 5
      src/types/home.d.ts
  24. 55
      src/types/system.d.ts
  25. 270
      src/views/home/index.vue

2
.env.prod

@ -3,5 +3,5 @@
FT_NODE_ENV=prod
FT_WS_URL=ws://192.168.8.168:8080/ws
FT_PROXY=http://192.168.8.88:8080
FT_PROXY=http://192.168.8.168:8080
FT_API_BASE=/api

4
.env.test

@ -2,6 +2,6 @@
FT_NODE_ENV=test
FT_WS_URL=ws://192.168.8.168:8080/ws
FT_PROXY=http://192.168.8.88:8080
FT_WS_URL=ws://192.168.1.199:8080/ws
FT_PROXY=http://192.168.1.199:8080
FT_API_BASE=/api

2
package.json

@ -1,7 +1,7 @@
{
"name": "matrix-spray-web",
"type": "module",
"version": "0.0.3",
"version": "0.0.4",
"description": "",
"author": "",
"license": "ISC",

3
src/apis/home.ts

@ -0,0 +1,3 @@
import http from 'libs/http'
export const setTargetTemperature = (params: Home.SetTargetTemperatureParams): Promise<null> => http.post('/heat/target-temperature', params)

1
src/apis/system.ts

@ -2,3 +2,4 @@ import http from 'libs/http'
export const debugControl = <T>(params: System.CmdControlParams<T>): Promise<null> => http.post('/debug/cmd', params)
export const control = <T>(params: System.CmdControlParams<T>): Promise<null> => http.post('/cmd', params)
export const getStatus = (): Promise<System.SystemStatus> => http.get('/sys/device-status')

12
src/app.vue

@ -1,14 +1,18 @@
<script setup lang="ts">
import { getStatus } from 'apis/system'
import { socket } from 'libs/socket'
import { useSystemStore } from 'stores/systemStore'
import { onBeforeUnmount, onMounted, ref } from 'vue'
onMounted(() => {
onMounted(async () => {
document.body.style.setProperty('--el-color-primary', '#1989fa')
const res = await getStatus()
useSystemStore().updateSystemStatus(res)
})
socket.init(() => {}, 'app')
// const router = useRouter()
socket.init((data: System.SystemStatus) => {
useSystemStore().updateSystemStatus(data)
}, 'status')
const progress = ref(0)
let timer: any = null

1
src/assets/images/empty.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="160" height="217" viewBox="0 0 160 217"><defs><clipPath id="master_svg0_701_8873"><rect x="0" y="0" width="160" height="217" rx="0"/></clipPath><linearGradient x1="0.8473145961761475" y1="-0.13048571348190308" x2="-0.21846741185260635" y2="0.45531219158762304" id="master_svg1_701_9703"><stop offset="0%" stop-color="#9CA0FF" stop-opacity="1"/><stop offset="100%" stop-color="#9CCFFF" stop-opacity="1"/></linearGradient><linearGradient x1="1.0152618885040283" y1="0.07957261055707932" x2="0.19026522035783114" y2="1.0885049011631125" id="master_svg2_701_9704"><stop offset="0%" stop-color="#F2F3FF" stop-opacity="1"/><stop offset="100%" stop-color="#F2F9FF" stop-opacity="1"/></linearGradient></defs><g clip-path="url(#master_svg0_701_8873)"><g><rect x="41.62646484375" y="0" width="76.74667358398438" height="42.228694915771484" rx="5" fill="url(#master_svg1_701_9703)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><rect x="0" y="21.1171875" width="160" height="195.88375854492188" rx="5" fill="url(#master_svg2_701_9704)" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M108.87600390624999,94.94093875L51.01304390625,94.94093875C48.43994390625,94.94098875,46.35400390625,92.85729875,46.35400390625,90.28692875C46.35605003625,87.71803875,48.44143390625,85.6366722972,51.01304390625,85.63671875L108.87590390625,85.63671875C111.44750390625,85.6366722972,113.53290390625,87.71803875,113.53490390625,90.28692875C113.53500390625,92.85724875,111.44910390625,94.94098875,108.87600390624999,94.94093875Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M108.87600390624999,123.71047L51.01304390625,123.71047C48.43994390625,123.71052,46.35400390625,121.62683,46.35400390625,119.05646C46.35605003625,116.48757,48.44143390625,114.4062035472,51.01304390625,114.40625L108.87590390625,114.40625C111.44750390625,114.4062035472,113.53290390625,116.48757,113.53490390625,119.05646C113.53500390625,121.62678,111.44910390625,123.71052,108.87600390624999,123.71047Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g><g><path d="M108.87600390624999,152.48000125L51.01304390625,152.48000125C48.43994390625,152.48000125,46.35400390625,150.39631125,46.35400390625,147.82599125C46.35614304625,145.25715125,48.44143390625,143.17578125,51.01304390625,143.17578125L108.87590390625,143.17578125C111.44750390625,143.17578125,113.53280390625,145.25715125,113.53490390625,147.82599125C113.53500390625,150.39631125,111.44910390625,152.48000125,108.87600390624999,152.48000125Z" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/error.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><defs><clipPath id="master_svg0_685_8859"><rect x="0" y="0" width="80" height="80" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_685_8859)"><g><path d="M39.9853890625,4.94140625C20.7118890625,4.94140625,4.9462890625,20.70700625,4.9462890625,39.98050625C4.9462890625,59.25390625,20.7118890625,75.01950625,39.9853890625,75.01950625C59.2587890625,75.01950625,75.0243890625,59.25390625,75.0243890625,39.98050625C75.0243890625,20.70700625,59.2587890625,4.94140625,39.9853890625,4.94140625ZM56.7274890625,52.05080625C57.8993890625,53.22260625,57.8993890625,55.36330625,56.7274890625,56.52730625C56.1415890625,57.11330625,55.3681890625,57.50390625,54.3915890625,57.50390625C53.6103890625,57.50390625,52.6415890625,57.11330625,52.0556890625,56.52730625L40.7665890625,45.23830625L29.4774890625,56.52730625C28.3056890625,57.69920625,26.1650890625,57.69920625,25.0009890625,56.52730625C23.8368890625,55.35550625,23.8290890625,53.21480625,25.0009890625,52.05080625L36.2900890625,40.76170625L25.0009890625,29.47260625C24.4150890625,28.88670625,24.0243890625,28.11330625,24.0243890625,27.13670625C24.0243890625,26.35550625,24.4150890625,25.38670625,25.0009890625,24.80080625C26.1728890625,23.62890625,28.3134890625,23.62890625,29.4774890625,24.80080625L40.7665890625,36.089806249999995L52.0556890625,24.80080625C52.6415890625,24.21480625,53.4150890625,23.82420625,54.3915890625,23.82420625C55.1728890625,23.82420625,56.1415890625,24.21480625,56.7275890625,24.80080625C57.3134890625,25.38670625,57.7040890625,26.16020625,57.7040890625,27.13670625C57.7040890625,27.91800625,57.3134890625,28.88670625,56.7275890625,29.47260625L45.4384890625,40.76170625L56.7274890625,52.05080625Z" fill="#DF1515" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/ing.svg
File diff suppressed because it is too large
View File

1
src/assets/images/success.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="70" height="70" viewBox="0 0 70 70"><defs><clipPath id="master_svg0_685_8843"><rect x="0" y="0" width="70" height="70" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_685_8843)"><g><path d="M35,70C15.6712,70,0,54.3288,0,35C0,15.6712,15.6712,0,35,0C54.3288,0,70,15.6712,70,35C70,54.3288,54.3288,70,35,70ZM27.195,48.7433C28.6189,50.1676,30.9278,50.1676,32.3517,48.7433L54.4542,26.6408C55.8781,25.2169,55.8781,22.9081,54.4542,21.4842C53.0302,20.0602,50.7215,20.0602,49.2975,21.4842L29.6158,40.8508L20.8075,32.0425C19.3838,30.6166,17.0736,30.6158,15.6488,32.0405C14.2241,33.4653,14.225,35.7755,15.6508,37.1992L27.195,48.7433Z" fill="#14A656" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

1
src/assets/images/wait.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="80" height="80" viewBox="0 0 80 80"><defs><clipPath id="master_svg0_685_8855"><rect x="0" y="0" width="80" height="80" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_685_8855)"><g><path d="M40.00636015625,5.01953125C20.68606015625,5.01953125,5.01416254044,20.69143125,5.01416254044,40.01173125C5.01416254044,59.33203125,20.68606015625,74.99613125,40.00636015625,74.99613125C59.32666015625,74.99613125,74.99856015625,59.32423125,74.99856015625,40.00393125C74.99856015625,20.683631249999998,59.32666015625,5.01953125,40.00636015625,5.01953125ZM21.97506015625,45.97263125C18.678260156249998,45.97263125,16.00636015625,43.30073125,16.00636015625,40.00393125C16.00636015625,36.70703125,18.678260156249998,34.03513125,21.97506015625,34.03513125C25.27196015625,34.03513125,27.94386015625,36.70703125,27.94386015625,40.00393125C27.94386015625,43.30073125,25.27196015625,45.97263125,21.97506015625,45.97263125ZM39.92046015625,45.97263125C36.62356015625,45.97263125,33.95166015625,43.30073125,33.95166015625,40.00393125C33.95166015625,36.70703125,36.62356015625,34.03513125,39.92046015625,34.03513125C43.21726015625,34.03513125,45.88916015625,36.70703125,45.88916015625,40.00393125C45.88916015625,43.30073125,43.21726015625,45.97263125,39.92046015625,45.97263125ZM57.86576015625,45.97263125C54.56886015625,45.97263125,51.89696015625,43.30073125,51.89696015625,40.00393125C51.89696015625,36.70703125,54.56886015625,34.03513125,57.86576015625,34.03513125C61.16256015625,34.03513125,63.83446015625,36.70703125,63.83446015625,40.00393125C63.83446015625,43.30073125,61.16256015625,45.97263125,57.86576015625,45.97263125Z" fill="#EE8223" fill-opacity="1" style="mix-blend-mode:passthrough"/></g></g></svg>

2
src/components/common/FTDialog/index.vue

@ -15,7 +15,7 @@ const props = defineProps({
default: '50%',
},
okHandle: {
type: Boolean,
type: Function,
default: () => {},
},
})

176
src/components/home/AddLiquid/index.vue

@ -0,0 +1,176 @@
<script setup lang="ts">
import { getSolsList } from 'apis/solution'
import emptyIcon from 'assets/images/empty.svg'
import { FtMessage } from 'libs/message'
import { useHomeStore } from 'stores/homeStore'
import { onMounted, ref } from 'vue'
const emits = defineEmits(['ok', 'cancel'])
const homeStore = useHomeStore()
onMounted(() => {
getSols()
})
const form = ref<{
tubeNum: number
solutionId: number
volume: number
}>({})
const formRef = ref()
const rules = {
tubeNum: [
{ required: true, message: '请选择试管', trigger: 'change' },
],
solutionId: [
{ required: true, message: '请选择溶液', trigger: 'change' },
],
volume: [
{ required: true, message: '请输入容量', trigger: 'blur' },
],
}
const addList = ref<{ tubeNum: number, solutionList: { solutionId: number, volume: number }[] }[]>([])
const mergeAddList = () => {
const map = new Map<number, { solutionId: number, volume: number }[]>()
addList.value.forEach((item) => {
if (!map.has(item.tubeNum)) {
map.set(item.tubeNum, [...item.solutionList])
}
else {
map.get(item.tubeNum)!.push(...item.solutionList)
}
})
return Array.from(map.entries()).map(([tubeNum, solutionList]) => ({
tubeNum,
solutionList,
}))
}
const addHandle = async () => {
try {
const valid = await formRef.value.validate()
if (!valid) {
return
}
addList.value!.push({
tubeNum: form.value.tubeNum,
solutionList: [{
solutionId: form.value.solutionId,
volume: form.value.volume,
}],
})
}
catch (error) {
console.log(error)
}
}
const okHandle = async () => {
if (!addList.value.length) {
FtMessage.error('请为试管添加溶液')
return
}
console.log(mergeAddList())
const params = {
commandId: Date.now().toString(),
command: 'solution_add',
params: {
dataList: mergeAddList(),
},
}
await homeStore.sendControl(params)
// await debugStore.sendControl(params)
emits('ok')
}
const cancel = () => {
emits('cancel')
}
const solsList = ref<Solution.SolutionItem[]>([])
const getSols = async () => {
const res = await getSolsList()
solsList.value = res.list
}
</script>
<template>
<FtDialog visible title="添加溶液" width="60%" :ok-handle="okHandle" @cancel="cancel">
<el-row :gutter="20">
<el-col :span="12">
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto">
<el-form-item label="选择试管" prop="tubeNum">
<el-select v-model="form.tubeNum" placeholder="请选择试管">
<el-option v-for="item in 16" :key="item" :label="`试管${item}`" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="选择溶液" prop="solutionId">
<el-select v-model="form.solutionId" placeholder="请选择溶液">
<el-option v-for="item in solsList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="容量" prop="volume">
<el-input v-model="form.volume" placeholder="请输入容量">
<template #append>
ml
</template>
</el-input>
</el-form-item>
<el-form-item>
<div style="width: 100%;display: flex;justify-content: center">
<ft-button type="primary" @click="addHandle">
添加
</ft-button>
</div>
</el-form-item>
</el-form>
</el-col>
<el-col :span="12">
<template v-if="addList.length">
<el-tag v-for="(item, key) in addList" :key>
<div>
<span>试管{{ item.tubeNum }}</span>
<span>-</span>
<span>{{ solsList.find(i => i.id === item.solutionList[0].solutionId)!.name }}</span>
<span>添加{{ item.solutionList[0].volume }}ml</span>
</div>
<el-icon><Close /></el-icon>
</el-tag>
</template>
<div v-else style="width: 100%;height: 100%;display: flex; flex-direction: column;justify-content: center;align-items: center;color: #C6C6C6">
<img :src="emptyIcon" alt="" style="width: 80px;margin-bottom: 10px">
暂无添加
</div>
</el-col>
</el-row>
</FtDialog>
</template>
<style scoped lang="scss">
.el-tag {
margin-right: 5px;
}
.el-row {
height: 200px;
.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;
}
}
}
}
</style>

9
src/components/home/SelectCraft/index.vue

@ -1,4 +1,5 @@
<script setup lang="ts">
import { setCraft } from 'apis/crafts'
import { getOreList } from 'apis/ore'
import { FtMessage } from 'libs/message'
import { useHomeStore } from 'stores/homeStore'
@ -29,8 +30,12 @@ const okHandle = async () => {
if (!valid) {
return
}
// TODO
// await add(form.value)
for (let i = 0; i < homeStore.heatAreaList.filter(item => item.selected).length; i++) {
await setCraft({
heatId: homeStore.heatAreaList.filter(item => item.selected)[i].value,
craftId: form.value.craftId,
})
}
FtMessage.success('工艺已设定')
emits('ok')
}

15
src/components/home/SetTemperature/index.vue

@ -1,8 +1,15 @@
<script setup lang="ts">
import { setTargetTemperature } from 'apis/home'
import { getOreList } from 'apis/ore'
import { FtMessage } from 'libs/message'
import { onMounted, ref } from 'vue'
const props = defineProps({
id: {
type: String,
default: 'heat_module_01',
},
})
const emits = defineEmits(['ok', 'cancel'])
onMounted(() => {
@ -10,7 +17,7 @@ onMounted(() => {
})
const form = ref({
craftId: undefined,
temperature: 0,
})
const formRef = ref()
@ -26,8 +33,10 @@ const okHandle = async () => {
if (!valid) {
return
}
// TODO
// await add(form.value)
await setTargetTemperature({
...form.value,
moduleCode: props.id,
})
FtMessage.success('设置成功')
emits('ok')
}

2
src/components/home/Stop/index.vue

@ -20,7 +20,7 @@
justify-content: center;
align-items: center;
color: var(--el-color-danger);
font-size: 100px;
font-size: 50px;
z-index: 10000;
top: 0;
left: 0;

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

@ -1,12 +1,27 @@
<script setup lang="ts">
import { ref } from 'vue'
import errorIcon from 'assets/images/error.svg'
import ingIcon from 'assets/images/ing.svg'
import successIcon from 'assets/images/success.svg'
import waitIcon from 'assets/images/wait.svg'
import { useHomeStore } from 'stores/homeStore'
import { useSystemStore } from 'stores/systemStore'
import { computed, ref } from 'vue'
const props = withDefaults(defineProps<{ data: Home.HeatArea }>(), {
data: () => ({ label: 'A-1', value: 'heat_module_01', selected: false, temperature: undefined }),
const props = withDefaults(defineProps<{ data: System.HeatArea }>(), {
data: () => ({
moduleCode: 'heat_module_01',
trayStatus: 0,
heating: false,
capExist: false,
temperature: 0,
targetTemperature: 0,
}),
})
const emits = defineEmits(['selectChange', 'setTemperature'])
const homeStore = useHomeStore()
const systemStore = useSystemStore()
const mousedownHandle = (e: Event) => {
let event
if ('touches' in e) {
@ -27,8 +42,16 @@ const tubeSelect = () => {
emits('selectChange')
}
const hearInfo = computed(() => {
return homeStore.heatAreaList.find(item => item.value === props.data.moduleCode)
})
const craft = computed(() => {
return systemStore.systemStatus.tray.find(item => item.heatModuleId === props.data.moduleCode)?.crafts
})
const setTemperature = () => {
emits('setTemperature', props.data.value)
emits('setTemperature', props.data.moduleCode)
}
defineExpose({
@ -37,29 +60,52 @@ defineExpose({
</script>
<template>
<div class="tube" :class="{ 'tube-active': data.selected }">
<div class="tube" :class="{ 'tube-active': hearInfo?.selected, 'tube-shadow': data.trayStatus === 2 }">
<div
v-if="data.trayStatus !== 0 && craft?.state"
class="status" :class="{
'status-success': false,
'status-wait': craft?.state === 'READY',
'status-error': craft?.state === 'ERROR',
'status-ing': craft?.state === 'RUNNING',
}"
>
<img v-if="craft?.state === 'FINISHED'" :src="successIcon" alt="">
<img v-if="craft?.state === 'RUNNING'" :src="ingIcon" alt="">
<img v-if="craft?.state === 'READY'" :src="waitIcon" alt="">
<img v-if="craft?.state === 'ERROR'" :src="errorIcon" alt="">
<span class="status-name">{{ craft?.craft?.name || ' ' }}</span>
<span v-if="craft?.state === 'RUNNING'" class="status-text">工艺执行中</span>
<span v-if="craft?.state === 'READY'" class="status-text">工艺等待执行</span>
<span v-if="craft?.state === 'ERROR'" class="status-text">工艺执行错误</span>
<span v-if="craft?.state === 'FINISHED'" class="status-text">工艺执行成功</span>
</div>
<div class="header">
<span>{{ data.label }}</span>
<span>已放置</span>
<span>{{ hearInfo?.label }}</span>
<span v-show="data.trayStatus === 0">空置</span>
<span v-show="data.trayStatus === 1">已放置</span>
<span v-show="data.trayStatus === 2">已抬起</span>
</div>
<div class="tube-item" @mousedown.prevent="mousedownHandle" @touchstart.prevent="mousedownHandle">
<div v-if="false" class="tube-disable" />
<span v-for="item in 16" :key="item" class="tube-inner" :class="{ 'tube-inner-active': item <= activeTubeNum }" :index="item" />
<div v-if="data.trayStatus === 0" class="tube-disable" />
<span v-for="item in 16" :key="item" class="tube-inner" :class="{ 'tube-inner-active': item <= activeTubeNum }" />
</div>
<div v-if="data.temperature" class="temperature-box" @click="setTemperature">
<div v-if="data.targetTemperature" class="temperature-box" @click="setTemperature">
<span>
<span>目标温度: </span>
<span>{{ data.temperature }}</span>
<span>{{ data.targetTemperature }}</span>
<span></span>
</span>
<span>加热中</span>
<span v-show="data.heating" style="color: #FE0A0A ">加热中</span>
<span v-show="!data.heating" style="color: #1677FF">未加热</span>
</div>
<div v-else class="temperature-box" style="justify-content: center" @click="setTemperature">
点击设置目标温度
</div>
<div class="footer">
<span :class="{ 'active-footer': data.selected }"> 200</span>
<ft-button size="small" :type="data.selected ? 'primary' : 'default'" @click="tubeSelect">
<span :class="{ 'active-footer': hearInfo?.selected }"> {{ data.temperature }}</span>
<ft-button size="small" :type="hearInfo?.selected ? 'primary' : 'default'" @click="tubeSelect">
选择
</ft-button>
</div>
@ -70,6 +116,9 @@ defineExpose({
.tube-active {
border-color: #275EFB !important;
}
.tube-shadow {
box-shadow: 0 0 10px rgba(0,0,0,0.9);
}
.tube {
box-sizing: border-box;
width: 100%;
@ -82,7 +131,51 @@ defineExpose({
flex-direction: column;
justify-content: space-between;
border: 2px solid #E9F3FF;
transition: border 0.3s;
transition: all 0.3s;
position: relative;
overflow: hidden;
.status {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255,255,255,0.9);
z-index: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
border-radius: 10px;
img {
width: 50px;
margin: 30px 0 10px;
}
.status-name {
font-size: 14px;
margin: 10px 0 10px;
}
.status-text {
font-size: 16px;
font-weight: 700;
}
}
.status-wait {
background: rgba(242,235,231, 0.9);
border: 1px solid #EE8223;
color: #EE8223;
}
.status-error {
background: rgba(232,212,222, 0.9);
border: 1px solid #DF1515;
color: #DF1515;
}
.status-ing {
background: rgba(205,223,255, 0.9);
border: 1px solid #3E3A39;
color: #3E3A39;
}
.header {
display: flex;
justify-content: space-between;

25
src/hooks/useActivateDebug.ts

@ -0,0 +1,25 @@
import { useSystemStore } from 'stores/systemStore'
import { ref } from 'vue'
export const useActivateDebug = () => {
const systemStore = useSystemStore()
const logoClickCount = ref(0)
let clickTimeout: NodeJS.Timeout | null = null
const handleLogoClick = () => {
if (clickTimeout) {
clearTimeout(clickTimeout)
}
logoClickCount.value++
if (logoClickCount.value === 10) {
systemStore.updateDebug()
logoClickCount.value = 0 // 重置计数器
}
clickTimeout = setTimeout(() => {
logoClickCount.value = 0 // 重置计数器
}, 1000)
}
return {
handleLogoClick,
}
}

11
src/layouts/default.vue

@ -1,5 +1,7 @@
<script setup lang="ts">
import Exit from 'components/exit/index.vue'
import Stop from 'components/home/Stop/index.vue'
import { useActivateDebug } from 'hooks/useActivateDebug'
import { isClose } from 'libs/socket'
import { formatDateTime } from 'libs/utils'
import { authRoutes } from 'router/routes'
@ -7,6 +9,8 @@ import { useSystemStore } from 'stores/systemStore'
import { onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const { handleLogoClick } = useActivateDebug()
const systemStore = useSystemStore()
const router = useRouter()
@ -31,8 +35,8 @@ const onExitHandle = () => {
<el-container class="main">
<el-header class="header">
<div class="logo">
<img src="../assets/images/logo.svg" alt="">
<span class="title">长春黄金研究院有限公司</span>
<img src="../assets/images/logo.svg" alt="" @click="handleLogoClick">
<span class="title" @click="handleLogoClick">长春黄金研究院有限公司</span>
<img :class="systemStore.menuExpand ? 'expand-icon' : 'fold-icon'" src="../assets/images/expand.svg" alt="" @click="systemStore.updateMenuExpand()">
</div>
<div class="header-right">
@ -56,7 +60,7 @@ const onExitHandle = () => {
<el-container class="container">
<el-aside class="aside" :class="{ 'aside-off': !systemStore.menuExpand }">
<div
v-for="item in authRoutes"
v-for="item in authRoutes.filter(item => item.meta!.isDefault)"
:key="item.path"
class="aside-item"
:class="{ 'aside-item-active': router.currentRoute.value.path.includes(item.path) }"
@ -93,6 +97,7 @@ const onExitHandle = () => {
</el-footer>
<FtStream :visible="systemStore.streamVisible" />
<Exit ref="exitModalRef" />
<Stop v-if="systemStore.systemStatus.emergencyStop" />
</el-container>
</template>

4
src/router/routes.ts

@ -36,7 +36,7 @@ const authRoutes: RouteRecordRaw[] = [
name: 'systemLog',
component: () => import('views/home/index.vue'),
meta: {
isDefault: true,
isDefault: false,
title: '日志记录',
icon: n_log,
activeIcon: s_log,
@ -47,7 +47,7 @@ const authRoutes: RouteRecordRaw[] = [
name: 'experimentLog',
component: () => import('views/home/index.vue'),
meta: {
isDefault: true,
isDefault: false,
title: '实验记录',
icon: n_expe,
activeIcon: s_expe,

26
src/stores/homeStore.ts

@ -1,4 +1,8 @@
import { control } from 'apis/system'
import { FtMessage } from 'libs/message'
import { cmdNameMap } from 'libs/utils'
import { defineStore } from 'pinia'
import { useSystemStore } from 'stores/systemStore'
export const useHomeStore = defineStore('home', {
state: (): Home.HomeStore => ({
@ -7,43 +11,53 @@ export const useHomeStore = defineStore('home', {
label: 'A-1',
value: 'heat_module_01',
selected: false,
temperature: undefined,
},
{
label: 'A-2',
value: 'heat_module_02',
selected: false,
temperature: undefined,
},
{
label: 'A-3',
value: 'heat_module_03',
selected: false,
temperature: undefined,
},
{
label: 'A-4',
value: 'heat_module_04',
selected: false,
temperature: undefined,
},
{
label: 'A-5',
value: 'heat_module_05',
selected: false,
temperature: undefined,
},
{
label: 'A-6',
value: 'heat_module_06',
selected: false,
temperature: undefined,
},
],
}),
actions: {
selectChange(index: number) {
this.heatAreaList[index].selected = !this.heatAreaList[index].selected
console.log(this.heatAreaList)
},
async sendControl(params: System.CmdControlParams<{ [key: string]: any }>) {
if (!params.commandId) {
params.commandId = Date.now().toString()
}
const systemStore = useSystemStore()
systemStore.systemList = []
// 使用提取的 key 匹配 cmdNameMap
const cmdName = cmdNameMap[params.command as keyof typeof cmdNameMap] || params.command
await control(params)
systemStore.updateStreamVisible(true)
FtMessage.success(`[${cmdName}]已发送`)
},
},
persist: true,

181
src/stores/systemStore.ts

@ -3,6 +3,179 @@ import { defineStore } from 'pinia'
export const useSystemStore = defineStore('system', {
state: (): System.SystemStore => ({
systemStatus: {
virtual: false,
initComplete: true,
emergencyStop: false,
door: {
status: false,
},
gantryArm: {
idle: true,
},
solutionArea: {
idle: true,
shaking: false,
trayStatus: 0,
solutionContainer: [
{
id: 1,
type: 'solution',
empty: false,
full: false,
},
{
id: 2,
type: 'solution',
empty: false,
full: false,
},
{
id: 3,
type: 'solution',
empty: false,
full: false,
},
{
id: 4,
type: 'solution',
empty: false,
full: false,
},
{
id: 5,
type: 'solution',
empty: false,
full: false,
},
{
id: 6,
type: 'solution',
empty: false,
full: false,
},
{
id: 7,
type: 'solution',
empty: false,
full: false,
},
{
id: 8,
type: 'solution',
empty: false,
full: false,
},
{
id: 9,
type: 'neutralization',
empty: false,
full: false,
},
],
pumping: false,
},
heatModule: [
{
moduleCode: 'heat_module_01',
trayStatus: 1,
heating: false,
capExist: false,
temperature: 0,
targetTemperature: 0,
},
{
moduleCode: 'heat_module_02',
trayStatus: 1,
heating: false,
capExist: false,
temperature: 100,
targetTemperature: 0,
},
{
moduleCode: 'heat_module_03',
trayStatus: 2,
heating: true,
capExist: false,
temperature: 130,
targetTemperature: 0,
},
{
moduleCode: 'heat_module_04',
trayStatus: 2,
heating: false,
capExist: false,
temperature: 0,
targetTemperature: 0,
},
{
moduleCode: 'heat_module_05',
trayStatus: 1,
heating: false,
capExist: false,
temperature: 0,
targetTemperature: 0,
},
{
moduleCode: 'heat_module_06',
trayStatus: 0,
heating: false,
capExist: false,
temperature: 0,
targetTemperature: 0,
},
],
tray: [
{
uuid: '',
heatModuleId: 'heat_module_01',
inSolutionArea: false,
inHeatModule: true,
tubes: [
{
addSolution: true,
exists: true,
},
],
crafts: {
state: 'READY',
},
},
{
uuid: '',
heatModuleId: 'heat_module_02',
inSolutionArea: false,
inHeatModule: true,
tubes: [
{
addSolution: true,
exists: true,
},
],
crafts: {
state: 'RUNNING',
craft: {
id: 1,
name: '菱锌矿硫酸溶解法',
steps: '',
},
},
},
{
uuid: '',
heatModuleId: 'heat_module_03',
inSolutionArea: false,
inHeatModule: true,
tubes: [
{
addSolution: true,
exists: true,
},
],
crafts: {
state: 'ERROR',
},
},
],
},
systemUser: {
username: '',
@ -17,6 +190,12 @@ export const useSystemStore = defineStore('system', {
systemList: [],
}),
actions: {
updateDebug() {
this.isDebug = !this.isDebug
},
updateSystemStatus(data: System.SystemStatus) {
this.systemStatus = data
},
updateStreamVisible(bool: boolean) {
this.streamVisible = bool
},
@ -30,5 +209,5 @@ export const useSystemStore = defineStore('system', {
this.systemList.push(text)
},
},
persist: true,
persist: false,
})

5
src/types/home.d.ts

@ -6,6 +6,9 @@ declare namespace Home {
label: 'A-1' | 'A-2' | 'A-3' | 'A-4' | 'A-5' | 'A-6'
value: 'heat_module_01' | 'heat_module_02' | 'heat_module_03' | 'heat_module_04' | 'heat_module_05' | 'heat_module_06'
selected: boolean
temperature: number | undefined
}
interface SetTargetTemperatureParams {
moduleCode: string
temperature: number
}
}

55
src/types/system.d.ts

@ -1,6 +1,8 @@
declare namespace System {
import Craft = CraftTypes.Craft
interface SystemStore {
systemStatus: any
systemStatus: SystemStatus
systemList: Socket.NotificationData[]
streamVisible: boolean
isDebug: boolean
@ -8,6 +10,57 @@ declare namespace System {
systemUser: SystemUser
loginForm: LoginForm
}
interface SystemStatus {
virtual: boolean
initComplete: boolean
emergencyStop: boolean
door: {
status: boolean
}
gantryArm: {
idle: boolean
}
solutionArea: {
idle: boolean
shaking: boolean
trayStatus: 0 | 1
solutionContainer: SolutionContainer[]
pumping: boolean
}
heatModule: HeatArea[]
tray: Tray[]
}
interface Tray {
uuid: string
heatModuleId: 'heat_module_01' | 'heat_module_02' | 'heat_module_03' | 'heat_module_04' | 'heat_module_05' | 'heat_module_06'
inSolutionArea: boolean
inHeatModule: boolean
tubes: [
{
addSolution: boolean
exists: boolean
},
]
crafts: {
state: 'READY' | 'RUNNING' | 'PAUSED' | 'STOPPED' | 'ERROR' | 'FINISHED'
craft?: CraftTypes.Craft
currentIndex?: number
}
}
interface HeatArea {
moduleCode: 'heat_module_01' | 'heat_module_02' | 'heat_module_03' | 'heat_module_04' | 'heat_module_05' | 'heat_module_06'
trayStatus: 0 | 1 | 2
heating: boolean
capExist: boolean
temperature: number
targetTemperature: number
}
interface SolutionContainer {
id: number
type: 'solution' | 'neutralization'
empty: boolean
full: boolean
}
interface CmdControlParams<T> {
commandId: string
command: string

270
src/views/home/index.vue

@ -1,20 +1,24 @@
<script setup lang="ts">
import { startCraft } from 'apis/crafts'
import AddLiquid from 'components/home/AddLiquid/index.vue'
import SelectCraft from 'components/home/SelectCraft/index.vue'
import SetTemperature from 'components/home/SetTemperature/index.vue'
import StartExperiment from 'components/home/StartExperiment/index.vue'
import Tube from 'components/home/Tube/index.vue'
import { FtMessage } from 'libs/message'
import { useHomeStore } from 'stores/homeStore'
import { useSystemStore } from 'stores/systemStore'
import { ref } from 'vue'
const homeStore = useHomeStore()
const systemStore = useSystemStore()
const startVisible = ref(false)
const startExperimentHandle = () => {
// TODO
startVisible.value = true
}
// const startExperimentHandle = () => {
// // TODO
// startVisible.value = true
// }
const selectCraftVisible = ref(false)
const selectCraft = () => {
@ -26,23 +30,185 @@ const selectCraft = () => {
selectCraftVisible.value = true
}
const executeCraftHandle = () => {
const count = homeStore.heatAreaList.filter(item => item.selected).length
if (!count) {
const executeCraftHandle = async () => {
const selectedHeatAreas = homeStore.heatAreaList.filter(item => item.selected)
if (!selectedHeatAreas.length) {
FtMessage.warning('请选择加热区')
return
}
// TODO
// TODO
FtMessage.success('执行工艺成功')
for (let i = 0; i < selectedHeatAreas.length; i++) {
await startCraft({
heatId: selectedHeatAreas[i].value,
})
}
FtMessage.success('工艺已开始执行')
}
const setTemperatureVisible = ref(false)
let currentTemperatureId = ''
const currentTemperatureId = ref('')
const setTemperature = (id: string) => {
currentTemperatureId.value = id
setTemperatureVisible.value = true
currentTemperatureId = id
console.log('currentTemperatureId', currentTemperatureId)
}
const addLiquidVisible = ref(false)
const addLiquid = () => {
const count = homeStore.heatAreaList.filter(item => item.selected).length
if (!count) {
FtMessage.warning('请选择加热区')
return
}
addLiquidVisible.value = true
}
const door_open = async () => {
const params = {
commandId: Date.now().toString(),
command: 'door_open',
params: { },
}
await homeStore.sendControl(params)
}
const door_close = async () => {
const params = {
commandId: Date.now().toString(),
command: 'door_close',
params: { },
}
await homeStore.sendControl(params)
}
const move_to_heat_area = async () => {
const selectedHeatAreas = homeStore.heatAreaList.filter(item => item.selected)
if (!selectedHeatAreas.length || selectedHeatAreas.length > 1) {
FtMessage.warning('请选择一个加热区')
return
}
const params = {
commandId: Date.now().toString(),
command: 'move_to_heat_area',
params: {
heatId: selectedHeatAreas[0].value,
},
}
await homeStore.sendControl(params)
}
const move_to_solution_area = async () => {
const selectedHeatAreas = homeStore.heatAreaList.filter(item => item.selected)
if (!selectedHeatAreas.length || selectedHeatAreas.length > 1) {
FtMessage.warning('请选择一个加热区')
return
}
const params = {
commandId: Date.now().toString(),
command: 'move_to_solution_area',
params: {
heatId: selectedHeatAreas[0].value,
},
}
await homeStore.sendControl(params)
}
const heat_start = async () => {
const selectedHeatAreas = homeStore.heatAreaList.filter(item => item.selected)
if (!selectedHeatAreas.length || selectedHeatAreas.length > 1) {
FtMessage.warning('请选择一个加热区')
return
}
if (!selectedHeatAreas[0].temperature) {
FtMessage.warning('请先设置温度')
return
}
const params = {
commandId: Date.now().toString(),
command: 'heat_start',
params: {
heatId: selectedHeatAreas[0].value,
temperature: selectedHeatAreas[0].temperature,
},
}
await homeStore.sendControl(params)
}
const heat_stop = async () => {
const selectedHeatAreas = homeStore.heatAreaList.filter(item => item.selected)
if (!selectedHeatAreas.length || selectedHeatAreas.length > 1) {
FtMessage.warning('请选择一个加热区')
return
}
const params = {
commandId: Date.now().toString(),
command: 'heat_stop',
params: {
heatId: selectedHeatAreas[0].value,
},
}
await homeStore.sendControl(params)
}
const tray_up = async () => {
const selectedHeatAreas = homeStore.heatAreaList.filter(item => item.selected)
if (!selectedHeatAreas.length || selectedHeatAreas.length > 1) {
FtMessage.warning('请选择一个加热区')
return
}
const params = {
commandId: Date.now().toString(),
command: 'tray_up',
params: {
heatId: selectedHeatAreas[0].value,
},
}
await homeStore.sendControl(params)
}
const tray_down = async () => {
const selectedHeatAreas = homeStore.heatAreaList.filter(item => item.selected)
if (!selectedHeatAreas.length || selectedHeatAreas.length > 1) {
FtMessage.warning('请选择一个加热区')
return
}
const params = {
commandId: Date.now().toString(),
command: 'tray_down',
params: {
heatId: selectedHeatAreas[0].value,
},
}
await homeStore.sendControl(params)
}
const shake_start = async () => {
const params = {
commandId: Date.now().toString(),
command: 'shake_start',
params: {
},
}
await homeStore.sendControl(params)
}
const shake_stop = async () => {
const params = {
commandId: Date.now().toString(),
command: 'shake_stop',
params: {
},
}
await homeStore.sendControl(params)
}
const take_photo = async () => {
const params = {
commandId: Date.now().toString(),
command: 'take_photo',
params: {
},
}
await homeStore.sendControl(params)
}
</script>
@ -51,37 +217,42 @@ const setTemperature = (id: string) => {
<el-row :gutter="10">
<el-col :span="15">
<div class="page-left">
<Tube v-for="(item, index) in homeStore.heatAreaList" :key="item.value" :data="item" @select-change="homeStore.selectChange(index)" @set-temperature="setTemperature" />
<Tube v-for="(item, index) in systemStore.systemStatus.heatModule" :key="item.moduleCode" :data="item" @select-change="homeStore.selectChange(index)" @set-temperature="setTemperature" />
</div>
</el-col>
<el-col :span="9">
<div class="page-right">
<div class="top">
<div class="photo">
<div class="photo" @click="take_photo">
<el-icon><Camera /></el-icon>
<span>拍照</span>
</div>
</div>
<div class="button-box">
<el-row>
<el-col :span="24">
<ft-button>
开门
</ft-button>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<ft-button @click="startExperimentHandle">
始实验
<ft-button :click-handle="door_open">
开门
</ft-button>
</el-col>
<el-col :span="12">
<ft-button disabled>
停止实验
<ft-button :click-handle="door_close">
关门
</ft-button>
</el-col>
</el-row>
<!-- <el-row :gutter="10"> -->
<!-- <el-col :span="12"> -->
<!-- <ft-button @click="startExperimentHandle"> -->
<!-- 开始实验 -->
<!-- </ft-button> -->
<!-- </el-col> -->
<!-- <el-col :span="12"> -->
<!-- <ft-button disabled> -->
<!-- 停止实验 -->
<!-- </ft-button> -->
<!-- </el-col> -->
<!-- </el-row> -->
<el-row :gutter="10">
<el-col :span="12">
<ft-button @click="selectCraft">
@ -95,45 +266,59 @@ const setTemperature = (id: string) => {
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<ft-button>
<el-col :span="24">
<ft-button @click="addLiquid">
添加溶液
</ft-button>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<ft-button :click-handle="shake_start">
开始摇匀
</ft-button>
</el-col>
<el-col :span="12">
<ft-button>
摇匀
<ft-button :click-handle="shake_stop">
停止摇匀
</ft-button>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="8">
<ft-button>
<el-col :span="12">
<ft-button :click-handle="move_to_heat_area">
移至加热
</ft-button>
</el-col>
<el-col :span="8">
<ft-button>
<el-col :span="12">
<ft-button :click-handle="move_to_solution_area">
移至加液
</ft-button>
</el-col>
<el-col :span="8">
<ft-button>
移至特殊
</ft-button>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<ft-button>
<ft-button :click-handle="heat_start">
开始加热
</ft-button>
</el-col>
<el-col :span="12">
<ft-button>
<ft-button :click-handle="heat_stop">
停止加热
</ft-button>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<ft-button :click-handle="tray_up">
抬起托盘
</ft-button>
</el-col>
<el-col :span="12">
<ft-button :click-handle="tray_down">
降下托盘
</ft-button>
</el-col>
</el-row>
</div>
</div>
@ -142,7 +327,8 @@ const setTemperature = (id: string) => {
<StartExperiment v-if="startVisible" @ok="startVisible = false" @cancel="startVisible = false" />
<SelectCraft v-if="selectCraftVisible" @ok="selectCraftVisible = false" @cancel="selectCraftVisible = false" />
<SetTemperature v-if="setTemperatureVisible" @ok="setTemperatureVisible = false" @cancel="setTemperatureVisible = false" />
<SetTemperature v-if="setTemperatureVisible" :id="currentTemperatureId" @ok="setTemperatureVisible = false" @cancel="setTemperatureVisible = false" />
<AddLiquid v-if="addLiquidVisible" @ok="addLiquidVisible = false" @cancel="addLiquidVisible = false" />
</div>
</template>
@ -198,7 +384,7 @@ const setTemperature = (id: string) => {
justify-content: flex-end;
.el-row {
height: fit-content;
margin-bottom: 15px;
margin-bottom: 5px;
}
}
}

Loading…
Cancel
Save