Files
mixly3/boards/default_src/python_pyodide/others/teachableMachine/components/teachableModel.vue

214 lines
5.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { provide, ref } from 'vue';
import { CirclePlusFilled } from '@element-plus/icons-vue';
import { ElMessage, ElSplitter, ElSplitterPanel, ElCard, ElButton, ElIcon } from 'element-plus';
import ClassBox from './teachableModel/ClassBox.vue';
import CameraBox from './teachableModel/CameraBox.vue';
import ModelArea from './teachableModel/ModelArea.vue';
import TrainArea from './teachableModel/TrainArea.vue';
// import 'element-plus/theme-chalk/src/message.scss';
// import 'element-plus/theme-chalk/src/splitter.scss';
// import 'element-plus/theme-chalk/src/splitter-panel.scss';
// import 'element-plus/theme-chalk/src/card.scss';
// import 'element-plus/theme-chalk/src/button.scss';
// import 'element-plus/theme-chalk/src/icon.scss';
// 存放所有拍摄的内容
const shotList = ref([])
provide('shotList', shotList)
// 存放类别和每个类别内含有的图片样本
const picList = ref([
{ title: '类别 1', list: [], disabled: false },
{ title: '类别 2', list: [], disabled: false },
])
provide('picList', picList)
// 训练状态
const states = ref({
isTraining: 0, // 0为未开始1为训练中2为训练完成
})
provide('states', states)
// 向某个类别添加一个样本
async function handleAddSample(idx, data) {
// 类别已被禁用
if (picList.value[idx].disabled) {
ElMessage.error('该类别已被禁用')
return
}
// 可能传入 base64 字符串上传图片
if (data) {
// data 是 base64说明是上传图片
picList.value[idx].list.push(data)
return
}
// 否则是拍摄,调用摄像头拍摄
await handleParentShot()
// 检查是否成功拍摄到图片
if (
!shotList.value[shotList.value.length - 1]
|| shotList.value[shotList.value.length - 1] === 'data:,'
) {
ElMessage.error('未获取到有效样本')
return
}
// 拍摄到的图片添加到类别中
picList.value[idx].list.push(shotList.value[shotList.value.length - 1])
}
// 添加一个类别
function handleAddClass() {
picList.value.push({
title: `类别 ${picList.value.length + 1}`,
list: [],
disabled: false,
})
}
// 删除一个类别
function handleDeleteClass(idx) {
picList.value.splice(idx, 1)
}
// 禁用/启用一个类别
function handleDisableClass(idx) {
picList.value[idx].disabled = !picList.value[idx].disabled
}
// 重命名一个类别
function handleRenameClass(idx, newTitle) {
picList.value[idx].title = newTitle
}
// 清空一个类别的样本
function handleClearSamples(idx) {
picList.value[idx].list = []
}
// 显示模型区域与否
const showModelArea = ref(true)
// 令摄像头拍摄一张图片加入shotList
const cameraBoxRef = ref(null)
async function handleParentShot() {
await cameraBoxRef.value?.captureShot()
}
// 训练模型
const modelAreaRef = ref(null)
async function handleTrain() {
await modelAreaRef.value.train()
}
</script>
<template>
<div class="main-splitter">
<ElSplitter>
<ElSplitterPanel size="70%" :min="200">
<div class="class-area">
<ClassBox v-for="(item, idx) in picList" :key="idx" :title="item.title" :pic-list="item.list"
:disabled="item.disabled" @add-sample="(data) => handleAddSample(idx, data)" @delete-sample="
(url) => (picList[idx].list = picList[idx].list.filter((i) => i !== url))
" @delete-class="handleDeleteClass(idx)" @disable-class="handleDisableClass(idx)"
@rename-class="(newTitle) => handleRenameClass(idx, newTitle)"
@clear-samples="handleClearSamples(idx)">
<ElButton type="primary" size="mini" @click="handleAddSample(idx)">
添加样本
</ElButton>
</ClassBox>
<ElCard class="add-class-card" @click="handleAddClass">
<div class="add-class-content">
添加一个类别
<ElIcon>
<CirclePlusFilled />
</ElIcon>
</div>
</ElCard>
</div>
</ElSplitterPanel>
<ElSplitterPanel :min="200">
<CameraBox ref="cameraBoxRef" />
<transition name="fade-scale">
<ModelArea v-if="showModelArea" ref="modelAreaRef" @shot="handleParentShot" />
</transition>
<TrainArea @train="handleTrain" />
</ElSplitterPanel>
</ElSplitter>
</div>
</template>
<style scoped>
.title {
font-size: 24px;
font-weight: 500;
color: #011216;
margin: 10px;
padding: 10px;
border-radius: 5px;
background: linear-gradient(to right, #d7dee1, transparent, transparent, transparent);
text-align: left;
}
.teachable-model-container {
width: 80%;
height: 80vh;
margin: 20px;
padding: 5px;
border: 2px solid #000;
border-radius: 5px;
box-shadow: 0px 0px 10px #000;
}
.main-splitter {
height: 100%;
width: 100%;
box-shadow: var(--el-border-color-light) 0px 0px 10px;
user-select: none;
}
.add-class-card {
margin: 10px auto;
border: 1px dotted #000;
max-width: 50vw;
cursor: default;
user-select: none;
box-sizing: border-box;
}
.add-class-card:hover {
cursor: pointer;
color: #409eff;
}
.fade-scale-enter-active {
transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}
.fade-scale-leave-active {
transition: all 0.3s cubic-bezier(0.55, 0, 0.1, 1);
}
.fade-scale-enter-from {
opacity: 0;
transform: scale(0.8);
}
.fade-scale-enter-to {
opacity: 1;
transform: scale(1);
}
.fade-scale-leave-from {
opacity: 1;
transform: scale(1);
}
.fade-scale-leave-to {
opacity: 0;
transform: scale(0.8);
}
</style>