Browse Source

fix:实验和实验记录

master
guoapeng 3 months ago
parent
commit
9517da9a58
  1. 9
      src/apis/home.ts
  2. 7
      src/assets/styles/element.scss
  3. 7
      src/components/common/FTButton/index.vue
  4. 117
      src/components/common/FTTable/index.vue
  5. 4
      src/components/home/SetTemperature/index.vue
  6. 6
      src/components/home/StartExperiment/index.vue
  7. 3
      src/components/home/Tube/index.vue
  8. 8
      src/hooks/useActivateDebug.ts
  9. 24
      src/layouts/default.vue
  10. 4
      src/router/index.ts
  11. 6
      src/router/routes.ts
  12. 4
      src/stores/systemStore.ts
  13. 1
      src/types/home.d.ts
  14. 2
      src/types/system.d.ts
  15. 26
      src/types/task.d.ts
  16. 4
      src/types/user.d.ts
  17. 3
      src/views/debug/index.vue
  18. 17
      src/views/home/index.vue
  19. 2
      src/views/point/index.vue
  20. 81
      src/views/taskLog/index.vue

9
src/apis/home.ts

@ -1,6 +1,11 @@
import http from 'libs/http' import http from 'libs/http'
const baseUrl = '/tasks/'
export const setTargetTemperature = (params: Home.SetTargetTemperatureParams): Promise<null> => http.post('/heat/target-temperature', params) export const setTargetTemperature = (params: Home.SetTargetTemperatureParams): Promise<null> => http.post('/heat/target-temperature', params)
export const trayIn = (): Promise<null> => http.post('/tray/in')
export const trayOut = (): Promise<null> => http.post('/tray/out')
export const trayTube = (params: Home.TrayTubeParams): Promise<null> => http.post('/tray/out', params) export const trayTube = (params: Home.TrayTubeParams): Promise<null> => http.post('/tray/out', params)
export const addTask = (params: Task.TaskAdd): Promise<null> => http.post(baseUrl, params)
export const stopTask = (): Promise<null> => http.post(`${baseUrl}stop`)
export const getTask = (id: number): Promise<Task.Task> => http.get(`${baseUrl}${id}`)
export const taskList = (params: Task.TaskQuery): Promise<Task.Task[]> => http.get(`${baseUrl}list`, { params })
export const getTaskIng = (): Promise<null> => http.get(`${baseUrl}getIngTask`)
export const delTask = (params: string): Promise<null> => http.delete(`${baseUrl}${params}`)

7
src/assets/styles/element.scss

@ -35,3 +35,10 @@
padding: 10px; padding: 10px;
} }
} }
.el-input-group__append {
padding: 0 10px;
.el-icon {
margin: 0;
}
}

7
src/components/common/FTButton/index.vue

@ -2,7 +2,7 @@
import { ref } from 'vue' import { ref } from 'vue'
interface FTButton { interface FTButton {
type?: 'default' | 'primary' | 'info'
type?: 'default' | 'primary' | 'info' | 'danger'
size?: 'small' | 'default' | 'large' size?: 'small' | 'default' | 'large'
disabled?: boolean disabled?: boolean
loading?: boolean loading?: boolean
@ -128,6 +128,11 @@ defineExpose({
color: #fff; color: #fff;
border: 1px solid #335AA5; border: 1px solid #335AA5;
} }
.my-button-danger {
background: #e74444;
color: #fff;
border: 1px solid #e74444;
}
.rotate-loading { .rotate-loading {
animation: spin 1s linear infinite; animation: spin 1s linear infinite;

117
src/components/common/FTTable/index.vue

@ -9,10 +9,28 @@ defineOptions({
const props = withDefaults(defineProps<TableProp>(), { const props = withDefaults(defineProps<TableProp>(), {
columns: () => [], columns: () => [],
mustInit: true, mustInit: true,
hasHeader: true,
hasHeader: false,
hasPage: false,
searchList: () => [],
getDataFn: () => Promise.resolve([]), getDataFn: () => Promise.resolve([]),
}) })
const emits = defineEmits([])
const emits = defineEmits([
'add',
'edit',
'detail',
'copy',
'sort',
'save',
'cancel',
'del',
'import',
'export',
'clickTreeNode',
'newTreeNode',
'editTreeNode',
'delTreeNode',
'rowClick',
])
enum ColumnType { enum ColumnType {
index = 'index', index = 'index',
selection = 'selection', selection = 'selection',
@ -32,6 +50,15 @@ interface Btn {
icon?: string icon?: string
type?: string type?: string
serverUrl: string serverUrl: string
serverCondition?: 0 | 1 | 2
}
interface Search {
name: string
icon?: string
key?: string
type?: string
serverUrl: string
} }
interface TableProp { interface TableProp {
@ -39,14 +66,14 @@ interface TableProp {
getDataFn: (params: any) => Promise<any> // getDataFn: (params: any) => Promise<any> //
mustInit?: boolean // mountedgetDataFn mustInit?: boolean // mountedgetDataFn
hasHeader?: boolean hasHeader?: boolean
hasPage?: boolean
btnList?: Btn[] btnList?: Btn[]
searchList?: Search[]
} }
// const attrs = useAttrs()
async function methodParent(fn: any) { async function methodParent(fn: any) {
const newFn = fn[0] === '/' ? fn.slice(1) : fn const newFn = fn[0] === '/' ? fn.slice(1) : fn
emits(newFn as never)
emits(newFn as never, state.selectedRows)
} }
onMounted(() => { onMounted(() => {
@ -59,43 +86,65 @@ const state = reactive({
loading: false, loading: false,
dataTotal: 0, dataTotal: 0,
tableData: [], tableData: [],
selectedRows: [],
filterObj: { pageSize: 10, pageNum: 1 },
}) })
function initData() { function initData() {
state.loading = true state.loading = true
props props
.getDataFn({})
.getDataFn(state.filterObj)
.then((data) => { .then((data) => {
console.log(data) console.log(data)
state.tableData = data
state.loading = false
state.tableData = props.hasPage ? data.list : data
state.dataTotal = data.total
}) })
.finally(() => { .finally(() => {
state.loading = false state.loading = false
}) })
} }
const handleSelectionChange = (val: any) => {
state.selectedRows = val
}
const rowClickHandle = (data: any) => {
emits('rowClick', data)
}
defineExpose({ defineExpose({
initData, initData,
}) })
</script> </script>
<template> <template>
<div class="ft-table">
<div v-if="hasHeader" class="header"> <div v-if="hasHeader" class="header">
<div v-for="btn in btnList" :key="btn.serverUrl">
<el-button :icon="btn.icon" :type="btn.type" @click="methodParent(btn.serverUrl)">
{{ btn.name }}
</el-button>
</div>
<div class="search">
<el-input>
<template #suffix>
<el-icon class="el-input__icon">
<search />
<div v-for="search in searchList" :key="search.key" class="search">
<el-input v-model="state.filterObj[search.key]" :placeholder="search.title" clearable>
<template #append>
<el-icon class="el-input__icon" @click="initData">
<Search />
</el-icon> </el-icon>
</template> </template>
</el-input> </el-input>
</div> </div>
<div v-for="btn in btnList" :key="btn.serverUrl">
<ft-button
:icon="btn.icon"
:type="btn.type"
:disabled="
!btn.serverCondition
? false
: btn.serverCondition === 1
? state.selectedRows.length !== 1
: state.selectedRows.length < 1
"
@click="methodParent(btn.serverUrl)"
>
{{ btn.name }}
</ft-button>
</div>
</div> </div>
<div class="table-main">
<el-table <el-table
v-loading="state.loading" v-loading="state.loading"
:="$attrs" :="$attrs"
@ -105,6 +154,8 @@ defineExpose({
:highlight-current-row="true" :highlight-current-row="true"
class="container-table" class="container-table"
header-row-class-name="header-row-class" header-row-class-name="header-row-class"
@selection-change="handleSelectionChange"
@row-click="rowClickHandle"
> >
<template v-for="(column, index) in columns" :key="column.key"> <template v-for="(column, index) in columns" :key="column.key">
<el-table-column <el-table-column
@ -121,23 +172,45 @@ defineExpose({
</el-table-column> </el-table-column>
</template> </template>
</el-table> </el-table>
</div>
<div class="table-page">
<el-pagination v-model:current-page="state.filterObj.pageNum" v-model:page-size="state.filterObj.pageSize" size="small" background layout="sizes, prev, pager, next" :total="state.dataTotal" @change="initData" />
</div>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.ft-table {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.header { .header {
display: flex; display: flex;
justify-content: space-between;
justify-content: flex-start;
align-items: center; align-items: center;
height: 40px;
margin-bottom: 10px;
.search { .search {
width: 200px; width: 200px;
} }
} }
.ft-button {
margin: 0 5px;
}
.table-main {
flex: 1;
overflow: auto;
}
.table-page {
margin-top: 10px;
display: flex;
justify-content: flex-end;
}
:deep(.header-row-class) { :deep(.header-row-class) {
th { th {
background-color: rgba(0,0,0,0.02 ) !important; background-color: rgba(0,0,0,0.02 ) !important;
//font-weight: 500;
//border-bottom: none;
color: rgba(0, 0, 0, 0.85) color: rgba(0, 0, 0, 0.85)
} }
} }

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

@ -27,7 +27,7 @@ const validateHandle = (rule: any, value: any, callback: any) => {
callback(new Error('请输入目标温度')) callback(new Error('请输入目标温度'))
} }
else if (value < 0 || value > 200) { else if (value < 0 || value > 200) {
callback(new Error('目标温度范围0-200'))
callback(new Error('目标温度范围0-200'))
} }
else { else {
callback() callback()
@ -70,7 +70,7 @@ const getOres = async () => {
</script> </script>
<template> <template>
<FtDialog visible title="设置温度" width="40%" :ok-handle="okHandle" @cancel="cancel">
<FtDialog visible title="设置目标温度" width="40%" :ok-handle="okHandle" @cancel="cancel">
<el-form ref="formRef" label-width="auto" :model="form" :rules="rules"> <el-form ref="formRef" label-width="auto" :model="form" :rules="rules">
<el-form-item label="目标温度" prop="temperature"> <el-form-item label="目标温度" prop="temperature">
<el-input v-model.number="form.temperature" type="number" placeholder="请输入温度"> <el-input v-model.number="form.temperature" type="number" placeholder="请输入温度">

6
src/components/home/StartExperiment/index.vue

@ -1,11 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { addTask } from 'apis/home'
import { FtMessage } from 'libs/message' import { FtMessage } from 'libs/message'
import { ref } from 'vue' import { ref } from 'vue'
const emits = defineEmits(['ok', 'cancel']) const emits = defineEmits(['ok', 'cancel'])
const form = ref({ const form = ref({
name: undefined,
name: '',
}) })
const formRef = ref() const formRef = ref()
@ -21,8 +22,7 @@ const okHandle = async () => {
if (!valid) { if (!valid) {
return return
} }
// TODO
// await add(form.value)
await addTask(form.value)
FtMessage.success('实验已开始') FtMessage.success('实验已开始')
emits('ok') emits('ok')
} }

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

@ -14,6 +14,7 @@ const props = withDefaults(defineProps<{ data: System.HeatArea }>(), {
moduleCode: 'heat_module_01', moduleCode: 'heat_module_01',
trayUp: 1, trayUp: 1,
trayStatus: 0, trayStatus: 0,
fanOpen: false,
heating: false, heating: false,
capExist: false, capExist: false,
temperature: 0, temperature: 0,
@ -151,7 +152,7 @@ defineExpose({
点击设置目标温度 点击设置目标温度
</div> </div>
<div class="footer"> <div class="footer">
<span :class="{ 'active-footer': hearInfo?.selected }"> {{ data.temperature }}</span>
<span :class="{ 'active-footer': hearInfo?.selected }"> {{ data.temperature || '--' }}</span>
<ft-button size="small" :type="hearInfo?.selected ? 'primary' : 'default'" @click="tubeSelect"> <ft-button size="small" :type="hearInfo?.selected ? 'primary' : 'default'" @click="tubeSelect">
选择 选择
</ft-button> </ft-button>

8
src/hooks/useActivateDebug.ts

@ -1,3 +1,4 @@
import { FtMessage } from 'libs/message'
import { useSystemStore } from 'stores/systemStore' import { useSystemStore } from 'stores/systemStore'
import { ref } from 'vue' import { ref } from 'vue'
@ -12,8 +13,15 @@ export const useActivateDebug = () => {
} }
logoClickCount.value++ logoClickCount.value++
if (logoClickCount.value === 10) { if (logoClickCount.value === 10) {
console.log('isDebug', systemStore.isDebug)
systemStore.updateDebug() systemStore.updateDebug()
logoClickCount.value = 0 // 重置计数器 logoClickCount.value = 0 // 重置计数器
if (systemStore.isDebug) {
FtMessage.success('已开启调试模式')
}
else {
FtMessage.error('已关闭调试模式')
}
} }
clickTimeout = setTimeout(() => { clickTimeout = setTimeout(() => {
logoClickCount.value = 0 // 重置计数器 logoClickCount.value = 0 // 重置计数器

24
src/layouts/default.vue

@ -7,7 +7,7 @@ import { isClose } from 'libs/socket'
import { formatDateTime } from 'libs/utils' 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 { onMounted, onUnmounted, ref } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const { handleLogoClick } = useActivateDebug() const { handleLogoClick } = useActivateDebug()
@ -22,7 +22,7 @@ const timeInterval = setInterval(() => {
}, 1000) }, 1000)
onMounted(() => { onMounted(() => {
if (!systemStore.systemStatus.selfTest && systemStore.systemUser.username !== 'test') {
if (!systemStore.systemStatus.selfTest && systemStore.systemStatus.currentUser?.username !== 'test') {
isCheck.value = true isCheck.value = true
} }
}) })
@ -30,6 +30,22 @@ onMounted(() => {
onUnmounted(() => { onUnmounted(() => {
clearInterval(timeInterval) clearInterval(timeInterval)
}) })
// metaisDefault=true isDebug=true,debug
const menuList = computed(() => {
return authRoutes.filter((item) => {
if (item.meta?.isDefault) {
return true
}
return !!(item.path === '/debug' && systemStore.isDebug)
})
})
watch(() => systemStore.isDebug, () => {
console.log('isDebug', systemStore.isDebug)
if (!systemStore.isDebug && router.currentRoute.value.path === '/debug') {
router.push('/')
}
})
const isCheck = ref(false) const isCheck = ref(false)
</script> </script>
@ -67,7 +83,7 @@ const isCheck = ref(false)
<el-dropdown class="user-dropdown" trigger="click"> <el-dropdown class="user-dropdown" trigger="click">
<div class="user-dropdown-item"> <div class="user-dropdown-item">
<img src="../assets/images/user.svg" alt=""> <img src="../assets/images/user.svg" alt="">
<span>{{ systemStore.systemUser.username }}</span>
<span>{{ systemStore.systemStatus.currentUser?.nickname || systemStore.systemStatus.currentUser?.username }}</span>
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
@ -86,7 +102,7 @@ const isCheck = ref(false)
<el-container class="container"> <el-container class="container">
<el-aside class="aside" :class="{ 'aside-off': !systemStore.menuExpand }"> <el-aside class="aside" :class="{ 'aside-off': !systemStore.menuExpand }">
<div <div
v-for="item in authRoutes.filter(item => item.meta!.isDefault)"
v-for="item in menuList"
:key="item.path" :key="item.path"
class="aside-item" class="aside-item"
:class="{ 'aside-item-active': router.currentRoute.value.path.includes(item.path) }" :class="{ 'aside-item-active': router.currentRoute.value.path.includes(item.path) }"

4
src/router/index.ts

@ -1,5 +1,6 @@
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router' import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
import { getToken } from '@/libs/token' import { getToken } from '@/libs/token'
import { useSystemStore } from 'stores/systemStore'
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import routes from './routes' import routes from './routes'
@ -9,7 +10,8 @@ const router = createRouter({
}) })
router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => { router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
if (getToken()) {
const systemStore = useSystemStore()
if (getToken() && systemStore.systemStatus.currentUser?.username) {
next() next()
} }
else { else {

6
src/router/routes.ts

@ -45,9 +45,9 @@ const authRoutes: RouteRecordRaw[] = [
{ {
path: '/experimentLog', path: '/experimentLog',
name: 'experimentLog', name: 'experimentLog',
component: () => import('views/home/index.vue'),
component: () => import('views/taskLog/index.vue'),
meta: { meta: {
isDefault: false,
isDefault: true,
title: '实验记录', title: '实验记录',
icon: n_expe, icon: n_expe,
activeIcon: s_expe, activeIcon: s_expe,
@ -118,7 +118,7 @@ const authRoutes: RouteRecordRaw[] = [
name: 'debug', name: 'debug',
component: () => import('views/debug/index.vue'), component: () => import('views/debug/index.vue'),
meta: { meta: {
isDefault: true,
isDefault: false,
title: '调试', title: '调试',
icon: n_debug, icon: n_debug,
activeIcon: s_debug, activeIcon: s_debug,

4
src/stores/systemStore.ts

@ -10,6 +10,8 @@ export const useSystemStore = defineStore('system', {
initComplete: true, initComplete: true,
selfTest: true, selfTest: true,
emergencyStop: false, emergencyStop: false,
currentUser: null,
currentTasks: null,
door: { door: {
open: false, open: false,
}, },
@ -82,7 +84,7 @@ export const useSystemStore = defineStore('system', {
{ {
moduleCode: 'heat_module_01', moduleCode: 'heat_module_01',
trayUp: 0, trayUp: 0,
trayStatus: 1,
trayStatus: 0,
heating: false, heating: false,
fanOpen: true, fanOpen: true,
capExist: false, capExist: false,

1
src/types/home.d.ts

@ -19,4 +19,5 @@ declare namespace Home {
exists: boolean exists: boolean
}[] }[]
} }
} }

2
src/types/system.d.ts

@ -15,6 +15,8 @@ declare namespace System {
initComplete: boolean initComplete: boolean
selfTest: boolean selfTest: boolean
emergencyStop: boolean emergencyStop: boolean
currentUser: User.User | null
currentTasks: Task.Task[] | null
door: { door: {
open: boolean open: boolean
} }

26
src/types/task.d.ts

@ -0,0 +1,26 @@
declare namespace Task {
interface TaskAdd {
name: string
}
interface TaskQuery extends System.Page {
name: string
}
interface Task {
id: number
name: string
status: 1 | 2
isDeleted: 0 | 1
createTime: string
startTime: string
endTime: string
updateTime: string
steps: Step[]
}
interface Step {
id: number
taskId: number
stepDescription: string
createTime: string
}
}

4
src/types/user.d.ts

@ -6,8 +6,8 @@ declare namespace User {
username: string username: string
nickname: string nickname: string
password?: string | null password?: string | null
role: string
deleted: string
role?: string
deleted?: string
fixedUser?: string fixedUser?: string
} }

3
src/views/debug/index.vue

@ -1255,9 +1255,6 @@ const savePositionVisible = ref(false)
//align-items: center; //align-items: center;
} }
:deep(.el-input-group__append) {
padding: 0 10px;
}
:deep(.el-card__header) { :deep(.el-card__header) {
background:rgba(0,0,0,0.03); background:rgba(0,0,0,0.03);
} }

17
src/views/home/index.vue

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { startCraft } from 'apis/crafts' import { startCraft } from 'apis/crafts'
import { stopTask } from 'apis/home'
import AddLiquid from 'components/home/AddLiquid/index.vue' import AddLiquid from 'components/home/AddLiquid/index.vue'
import FillSolution from 'components/home/FillSolution/index.vue' import FillSolution from 'components/home/FillSolution/index.vue'
import SelectCraft from 'components/home/SelectCraft/index.vue' import SelectCraft from 'components/home/SelectCraft/index.vue'
@ -34,10 +35,22 @@ const receiveMessage = (data: Socket.cmdData) => {
const startVisible = ref(false) const startVisible = ref(false)
const startExperimentHandle = () => { const startExperimentHandle = () => {
// TODO
if (systemStore.systemStatus.currentTasks) {
FtMessage.error('当前有实验正在进行')
return
}
startVisible.value = true startVisible.value = true
} }
const stopExperimentHandle = async () => {
if (!systemStore.systemStatus.currentTasks) {
FtMessage.error('当前没有实验正在进行')
return
}
await stopTask()
FtMessage.success('实验已停止')
}
const selectCraftVisible = ref(false) const selectCraftVisible = ref(false)
const selectCraft = () => { const selectCraft = () => {
const count = homeStore.heatAreaList.filter(item => item.selected).length const count = homeStore.heatAreaList.filter(item => item.selected).length
@ -496,7 +509,7 @@ const take_photo = async () => {
</ft-button> </ft-button>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<ft-button disabled>
<ft-button :click-handle="stopExperimentHandle">
停止实验 停止实验
</ft-button> </ft-button>
</el-col> </el-col>

2
src/views/point/index.vue

@ -45,7 +45,7 @@ const columns = [
<template> <template>
<div> <div>
<FtTable :has-header="false" :columns="columns" :get-data-fn="getPointList" />
<FtTable :columns="columns" :get-data-fn="getPointList" />
<Edit v-if="upDataVisible" @ok="upDataVisible = false" @cancel="upDataVisible = false" /> <Edit v-if="upDataVisible" @ok="upDataVisible = false" @cancel="upDataVisible = false" />
</div> </div>
</template> </template>

81
src/views/taskLog/index.vue

@ -0,0 +1,81 @@
<script setup lang="ts">
import { delTask, taskList } from 'apis/home'
import { ElMessageBox, ElTag } from 'element-plus'
import { FtMessage } from 'libs/message'
import { h, useTemplateRef } from 'vue'
const searchList = [
{
title: '实验名称',
key: 'name',
},
]
const btnList = [
{
name: '删除',
type: 'danger',
serverUrl: 'del',
serverCondition: 2,
},
]
const columns = [
{
title: '实验名称',
type: 'selection',
},
{
title: '实验名称',
key: 'name',
},
{
title: '开始时间',
key: 'startTime',
},
{
title: '结束时间',
key: 'endTime',
},
{
title: '状态',
key: 'status',
width: 120,
render: (row: any) => {
return h(
ElTag,
{
type: row.status === 1 ? 'primary' : 'success',
},
{ default: () => row.status === 1 ? '执行中' : '执行完毕' },
)
},
},
]
const tableRef = useTemplateRef('tableRef')
const del = async (selectedRows: any) => {
const ids = selectedRows.map((item: any) => item.id)
await ElMessageBox.confirm('确定删除当前选中的行?', '消息', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
await delTask(ids)
FtMessage.success('删除成功')
tableRef.value?.initData()
}
const rowClickHandle = (data: Task.Task) => {
console.log(data)
}
</script>
<template>
<div>
<FtTable ref="tableRef" has-header has-page :columns="columns" :btn-list="btnList" :search-list="searchList" :get-data-fn="taskList" @del="del" @row-click="rowClickHandle" />
</div>
</template>
<style scoped lang="scss">
</style>
Loading…
Cancel
Save