8 changed files with 363 additions and 31 deletions
-
21src/src/main/java/com/my/graphiteDigesterBg/api/ApiCamera.java
-
45src/src/main/java/com/my/graphiteDigesterBg/api/ApiDigestionPreset.java
-
3src/src/main/java/com/my/graphiteDigesterBg/diframe/mapper/DiActiveRecordMapper.java
-
BINsrc/web/src/assets/img/camera-demo.png
-
35src/web/src/pages/main/contents/Operation.vue
-
100src/web/src/pages/main/contents/OperationCamera.vue
-
169src/web/src/pages/main/contents/TaskStepManagement.vue
-
17src/web/src/utils/ApiClient.js
@ -0,0 +1,21 @@ |
|||
package com.my.graphiteDigesterBg.api; |
|||
import com.my.graphiteDigesterBg.diframe.DiApiControllerBase; |
|||
import com.my.graphiteDigesterBg.diframe.DiApiResponse; |
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import java.nio.ByteBuffer; |
|||
import java.util.Base64; |
|||
import java.util.Map; |
|||
@Controller |
|||
public class ApiCamera extends DiApiControllerBase { |
|||
@ResponseBody |
|||
@PostMapping("/api/camera/image") |
|||
public DiApiResponse getImage() { |
|||
// ByteBuffer buffer = ByteBuffer.allocate(1024); |
|||
// byte[] bytes = buffer.array(); |
|||
// String base64Text = Base64.getEncoder().encodeToString(bytes); |
|||
String base64Data = "data:image/image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAUCAYAAACJfM0wAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAAH8SURBVDiNtZM/aBNxFMc/791PLJihQ4QMFhJQ7ODgmIJDbxAEXQoJuAqSzOLSQ4c4lFAoOGSQdHEtpOBSQXDo4pChg2NBQQfBDBkyKLRw93sOl1KT3LVC4ne5433f+7w/x0GO2t39Tnu3d5jr7/YO2939Tp4vAK3X75avXosf+sTfOjNUFTx4fGahoqDg/bmvgX45/e3et55tjGTrzd4dleAjQmmi0hNC/B113zLJPq6AK6NMbmUMvCX3nQZBB5uCziOhpBp0HMZ6ZkLAc/yVIVg2QN0LhFKmbay73M7GIyQHmuppXk+AM/AnYnuCJvFFpEvlA4eTt8C9M3BRHLe9BL/m4apSMCjC+cSrhhxcvPnl+rtc50PlK/PjiUjfsBOMVaAEnCD0x2NVgSVggHAsyJKZVacZsxOb9DcbtbWoUQ9Vkw0A7/121KiHUaMeeu+3AVSTjahRDzcbtTVM+jPDtbu9ycsaMUJ//LwB3DQYifE57ctdgWXgK8YPBIdRRSa3nz1FmlBGJAYrpiEKiJTTdyuMM0uIODA3Dc08hcBR1KyvRM1aJTEJ05jsRM1aJWrWKoLsACQmYRqrrwgc/esp9jCLgVVEqhgDsA/jkgfpr2x94BgRh/F4eupZ8IKkAqP/wB2qGd3Fc62rp4PiS7AtYLgA4tDDq9Of11t/AC8CsohkwY6rAAAAAElFTkSuQmCC"; |
|||
return this.success(Map.of("data",base64Data)); |
|||
} |
|||
} |
@ -0,0 +1,45 @@ |
|||
package com.my.graphiteDigesterBg.api; |
|||
import com.my.graphiteDigesterBg.diframe.DiActiveRecord; |
|||
import com.my.graphiteDigesterBg.diframe.DiActiveRecordCriteria; |
|||
import com.my.graphiteDigesterBg.diframe.DiApiControllerBase; |
|||
import com.my.graphiteDigesterBg.diframe.DiApiResponse; |
|||
import com.my.graphiteDigesterBg.model.MdbDigestionTask; |
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import java.util.Map; |
|||
@Controller |
|||
public class ApiDigestionPreset extends DiApiControllerBase { |
|||
@ResponseBody |
|||
@PostMapping("/api/digestion-preset/save") |
|||
public DiApiResponse save( @RequestBody Map<String,Object> params ) { |
|||
Integer id = (Integer)params.get("id"); |
|||
Map<String,Object> data = (Map<String,Object>)params.get("data"); |
|||
|
|||
MdbDigestionTask preset = new MdbDigestionTask(); |
|||
if ( id != null ) { |
|||
preset = DiActiveRecord.findOne(MdbDigestionTask.class, id); |
|||
} |
|||
preset.setAttributes(data); |
|||
preset.save(); |
|||
return this.success(); |
|||
} |
|||
|
|||
@ResponseBody |
|||
@PostMapping("/api/digestion-preset/list") |
|||
public DiApiResponse list() { |
|||
var criteria = new DiActiveRecordCriteria(); |
|||
var list = DiActiveRecord.find(MdbDigestionTask.class, criteria); |
|||
return this.success(list); |
|||
} |
|||
|
|||
@ResponseBody |
|||
@PostMapping("/api/digestion-preset/delete") |
|||
public DiApiResponse delete( @RequestBody Map<String,Object> params ) { |
|||
Integer id = (Integer)params.get("id"); |
|||
MdbDigestionTask preset = DiActiveRecord.findOne(MdbDigestionTask.class, id); |
|||
preset.delete(); |
|||
return this.success(); |
|||
} |
|||
} |
After Width: 1029 | Height: 904 | Size: 851 KiB |
@ -0,0 +1,100 @@ |
|||
<template> |
|||
<div class="h-full flex flex-col bg-white rounded-2xl p-5"> |
|||
<div class="camera h-0 grow rounded-2xl flex flex-col justify-center items-center"> |
|||
<div v-if="null === imageData"> |
|||
<p class="m-0 text-center"><img src="../../../assets/icon/camera-off.svg" /></p> |
|||
<p class="m-0 mt-2 text-2xl text-white">未检测到照相设备</p> |
|||
</div> |
|||
<img v-else :src="imageData" class="w-full rounded-xl" /> |
|||
</div> |
|||
<div class="mt-3 flex flex-row justify-between"> |
|||
<div> |
|||
<a-button @click="actionTakeShot"><CameraOutlined /></a-button> |
|||
</div> |
|||
<div> |
|||
<a-button><SplitCellsOutlined /></a-button> |
|||
<a-button class="ml-1"><MergeCellsOutlined /></a-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 拍照 --> |
|||
<a-modal v-if="imageModalOpen" v-model:open="imageModalOpen" title="拍照结果" width="80%" :footer="null"> |
|||
<a-row> |
|||
<a-col :span="12"> |
|||
<img :src="imageShotData" class="w-full rounded-xl" /> |
|||
</a-col> |
|||
<a-col :span="12" class="p-5 pt-0"> |
|||
<a-row class="mb-5" style="border: solid 1px #d9d9d9;padding: 10px;border-radius: 10px;background: #dddddd;"> |
|||
<a-col :span="6" v-for="i in 16" :key="i" class="mb-5 text-center"> |
|||
<a-button shape="circle" size="large" |
|||
:type="errorTubes.includes(i) ? 'primary' : 'default'" |
|||
@click="actionTubeErrorToggle(i)" |
|||
><ExperimentOutlined /></a-button> |
|||
</a-col> |
|||
</a-row> |
|||
<div class="text-right"> |
|||
<a-button>移动至异常区</a-button> |
|||
</div> |
|||
</a-col> |
|||
</a-row> |
|||
</a-modal> |
|||
</template> |
|||
<script setup> |
|||
import ApiClient from '@/utils/ApiClient'; |
|||
import { onMounted, onUnmounted, ref } from 'vue'; |
|||
/** @var {Boolean} */ |
|||
const imageModalOpen = ref(false); |
|||
/** @var {Object} */ |
|||
const imageShotData = ref(null); |
|||
/** @var {Array} */ |
|||
const errorTubes = ref([]); |
|||
/** @var {String} */ |
|||
const imageData = ref(null); |
|||
/** @var {Number} */ |
|||
let refreshTimer = null; |
|||
/** @var {ApiClient} */ |
|||
let client = null; |
|||
// on mounted |
|||
onMounted(mounted); |
|||
// on unmounted |
|||
onUnmounted(unmounted); |
|||
|
|||
// on mounted |
|||
function mounted() { |
|||
client = ApiClient.getClient(); |
|||
refresh(); |
|||
} |
|||
|
|||
// on unmounted |
|||
function unmounted() { |
|||
if ( refreshTimer ) { |
|||
clearTimeout(refreshTimer); |
|||
} |
|||
} |
|||
|
|||
// refresh |
|||
async function refresh() { |
|||
let response = await client.call('camera/image'); |
|||
imageData.value = response.data; |
|||
refreshTimer = setTimeout(() => refresh(), 200); |
|||
} |
|||
|
|||
// 拍照 |
|||
function actionTakeShot() { |
|||
imageModalOpen.value = true; |
|||
imageShotData.value = imageData.value; |
|||
} |
|||
|
|||
// 异常区 |
|||
function actionTubeErrorToggle( index ) { |
|||
if ( errorTubes.value.includes(index) ) { |
|||
errorTubes.value.splice(errorTubes.value.indexOf(index), 1); |
|||
} else { |
|||
errorTubes.value.push(index); |
|||
} |
|||
} |
|||
</script> |
|||
<style> |
|||
.camera {background: linear-gradient(180deg, rgba(5, 10, 39, 0.5) 0%, rgba(4, 10, 52, 0.5) 97%);backdrop-filter: blur(194px);} |
|||
</style> |
@ -1,21 +1,168 @@ |
|||
<template> |
|||
<div class="p-1"> |
|||
<a-table :dataSource="dataSource" :columns="columns"></a-table> |
|||
<a-table :dataSource="dataSource" :columns="columns"> |
|||
<template #headerCell="{ column }"> |
|||
<template v-if="column.key === 'action'"> |
|||
<PlusCircleOutlined @click="actionCreate"/> |
|||
</template> |
|||
</template> |
|||
<template #bodyCell="{ column, record }"> |
|||
<template v-if="column.key === 'action'"> |
|||
<a-popconfirm title="是否删除当前预设 ?" @confirm="actionDelete(record)"> |
|||
<DeleteOutlined /> |
|||
</a-popconfirm> |
|||
<FormOutlined @click="actionEdit(record)" class="ml-5"/> |
|||
</template> |
|||
</template> |
|||
</a-table> |
|||
</div> |
|||
|
|||
<!-- edit modal --> |
|||
<a-modal v-if="edit.enable" v-model:open="edit.enable" title="预设编辑" @ok="actionEditOk" ok-text="确定" cancel-text="取消"> |
|||
<p><a-input v-model:value="edit.data.name" placeholder="名称" /></p> |
|||
<a-row class="py-3"> |
|||
<a-col :span="12">执行步骤</a-col> |
|||
<a-col :span="12" class="text-right"> |
|||
<a-button size="small" @click="actionStepAdd"><PlusCircleOutlined /></a-button> |
|||
</a-col> |
|||
</a-row> |
|||
<a-collapse accordion v-model:activeKey="editActiveStepKey"> |
|||
<a-collapse-panel v-for="(step,index) in edit.data.steps" :key="index"> |
|||
<template #header> |
|||
步骤 : {{ index + 1 }} - {{ stepNameGet(step) }} |
|||
</template> |
|||
<template #extra><delete-outlined @click="actionStepDelete($event, index)" /></template> |
|||
<a-form :label-col="{span:4}" :wrapper-col="{span:20}"> |
|||
<a-form-item label="操作"> |
|||
<a-radio-group v-model:value="step.action" button-style="solid"> |
|||
<a-radio-button value="Heating">加热</a-radio-button> |
|||
<a-radio-button value="Pump">加酸</a-radio-button> |
|||
</a-radio-group> |
|||
</a-form-item> |
|||
<a-form-item label="目标温度" v-if="'Heating' === step.action"> |
|||
<a-input-number v-model:value="step.temperature" addon-after="℃" /> |
|||
</a-form-item> |
|||
<a-form-item label="持续时间" v-if="'Heating' === step.action"> |
|||
<a-input-number v-model:value="step.duration" addon-after="秒" /> |
|||
</a-form-item> |
|||
<a-form-item label="酸液类型" v-if="'Pump' === step.action"> |
|||
<a-select v-model:value="step.type"> |
|||
<a-select-option value="hydrochloric">盐酸</a-select-option> |
|||
<a-select-option value="nitric">硝酸</a-select-option> |
|||
<a-select-option value="sulfuric">硫酸</a-select-option> |
|||
<a-select-option value="hydrofluoric">氢氟酸</a-select-option> |
|||
<a-select-option value="perchloric">高氯酸</a-select-option> |
|||
<a-select-option value="hydrobromic">液溴</a-select-option> |
|||
<a-select-option value="phosphoric">磷酸</a-select-option> |
|||
<a-select-option value="tartaric">酒石酸</a-select-option> |
|||
</a-select> |
|||
</a-form-item> |
|||
<a-form-item label="加酸量" v-if="'Pump' === step.action"> |
|||
<a-input-number v-model:value="step.amount" /> |
|||
</a-form-item> |
|||
<a-form-item label="摇匀次数" v-if="'Pump' === step.action"> |
|||
<a-input-number v-model:value="step.shake" /> |
|||
</a-form-item> |
|||
</a-form> |
|||
</a-collapse-panel> |
|||
</a-collapse> |
|||
</a-modal> |
|||
</template> |
|||
<script setup> |
|||
import { ref } from 'vue'; |
|||
import ApiClient from '@/utils/ApiClient'; |
|||
import { onMounted, ref, nextTick } from 'vue'; |
|||
/** @var {Array<Object>} */ |
|||
const columns = [ |
|||
{key:'name',dataIndex:'name',title:'名称',align:'center'}, |
|||
{key:'steps',dataIndex:'steps',title:'步骤',align:'left'}, |
|||
{key:'action',dataIndex:'action',title:'操作',align:'center'}, |
|||
{key:'name',dataIndex:'name',title:'名称',align:'left',width:200}, |
|||
{key:'steps',dataIndex:'stepSummary',title:'步骤',align:'left'}, |
|||
{key:'action',dataIndex:'action',align:'right',width:80}, |
|||
]; |
|||
/** @var {Array<Object>} */ |
|||
const dataSource = ref([ |
|||
{name:'预设001',steps:'加酸:硫酸 -> 加热: 270℃ 15分钟 -> 加酸:硫酸 -> 加热: 270℃ 15分钟 -> 加酸:硫酸 -> 加热: 270℃ 15分钟',action:'删除 编辑'}, |
|||
{name:'预设002',steps:'加酸:盐酸 -> 加热: 270℃ 15分钟 -> 加酸:硫酸 -> 加热: 270℃ 15分钟 加热: 270℃ 15分钟',action:'删除 编辑'}, |
|||
{name:'预设003',steps:'加酸:氢氟酸 -> 加热: 270℃ 15分钟 -> 加酸:盐酸 -> 加热: 270℃ 15分钟 -> 加酸:盐酸 -> 加热: 270℃ 15分钟',action:'删除 编辑'}, |
|||
{name:'预设004',steps:'加酸:盐酸 -> 加热: 270℃ 15分钟 -> 加酸:氢氟酸 -> 加热: 270℃ 15分钟 -> 加酸:盐酸 -> 加热: 270℃ 15分钟',action:'删除 编辑'}, |
|||
]); |
|||
const dataSource = ref([]); |
|||
/** @var {Ref<Object>} */ |
|||
const edit = ref({enable:false,id:null,data:{name:'',steps:[]}}); |
|||
/** @var {Ref<String>} */ |
|||
const editActiveStepKey = ref(null); |
|||
// mounted |
|||
onMounted(mounted); |
|||
|
|||
// mounted |
|||
async function mounted() { |
|||
await refresh(); |
|||
} |
|||
|
|||
// step name get |
|||
function stepNameGet(step) { |
|||
let acidMap = {hydrochloric:'盐酸',nitric:'硝酸',sulfuric:'硫酸',hydrofluoric:'氢氟酸',perchloric:'高氯酸',hydrobromic:'液溴',phosphoric:'磷酸',tartaric:'酒石酸'}; |
|||
if ( 'Heating' === step.action ) { |
|||
return `加热 ${step.temperature}℃ ${step.duration}秒`; |
|||
} else if ( 'Pump' === step.action ) { |
|||
return `加酸 ${acidMap[step.type]} ${step.amount}ml 摇匀${step.shake}次`; |
|||
} |
|||
} |
|||
|
|||
// 刷新 |
|||
async function refresh() { |
|||
let client = ApiClient.getClient(); |
|||
dataSource.value = []; |
|||
let list = await client.digestionPresetList(); |
|||
for ( let item of list ) { |
|||
item.steps = JSON.parse(item.steps); |
|||
item.stepSummary = []; |
|||
for ( let sitem in item.steps ) { |
|||
item.stepSummary.push(stepNameGet(item.steps[sitem])); |
|||
} |
|||
item.stepSummary = item.stepSummary.join(' -> '); |
|||
|
|||
} |
|||
dataSource.value = list; |
|||
} |
|||
|
|||
// create |
|||
function actionCreate() { |
|||
edit.value.enable = true; |
|||
edit.value.id = null; |
|||
edit.value.data = {}; |
|||
edit.value.data.name = '未命名预设'; |
|||
edit.value.data.steps = [{action:'Heating',temperature:270,duration:15,type:'sulfuric',amount:1000,shake:5}]; |
|||
} |
|||
|
|||
// 添加步骤 |
|||
function actionStepAdd() { |
|||
edit.value.data.steps.push({action:'Heating',temperature:270,duration:15,type:'sulfuric',amount:1000,shake:5}); |
|||
} |
|||
|
|||
// 删除步骤 |
|||
function actionStepDelete(event, index) { |
|||
event.stopPropagation(); |
|||
event.preventDefault(); |
|||
edit.value.data.steps.splice(index,1); |
|||
} |
|||
|
|||
// edit ok |
|||
async function actionEditOk() { |
|||
edit.value.enable = false; |
|||
let steps = JSON.stringify(edit.value.data.steps); |
|||
let params = {}; |
|||
params.id = edit.value.id; |
|||
params.data = edit.value.data; |
|||
params.data.steps = steps; |
|||
let client = ApiClient.getClient(); |
|||
await client.digestionPresetSave(params); |
|||
await refresh(); |
|||
} |
|||
|
|||
// 删除 |
|||
async function actionDelete(record) { |
|||
let client = ApiClient.getClient(); |
|||
await client.digestionPresetDelete(record.id); |
|||
await refresh(); |
|||
} |
|||
|
|||
// 编辑 |
|||
async function actionEdit(record) { |
|||
edit.value.enable = true; |
|||
edit.value.id = record.id; |
|||
edit.value.data = record; |
|||
} |
|||
</script> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue