增加视觉平台

增加报警等后期增加的功能
master
LAPTOP-S9HJSOEB\昊天 6 months ago
parent 1c257a36bd
commit 17451a335f

@ -5,7 +5,7 @@ VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://127.0.0.1:48080'
VITE_BASE_PORT='48081'
VITE_BASE_PORT='48080'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server

@ -5,7 +5,7 @@ VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://192.168.0.162:48080'
VITE_BASE_PORT='48081'
VITE_BASE_PORT='48080'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server

@ -5,7 +5,7 @@ VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://192.168.0.162:48080'
VITE_BASE_PORT='48081'
VITE_BASE_PORT='48080'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server

@ -5,7 +5,7 @@ VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://192.168.0.162:48080'
VITE_BASE_PORT='48081'
VITE_BASE_PORT='48080'
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务
VITE_UPLOAD_TYPE=server

2360
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -25,10 +25,14 @@
"lint:lint-staged": "lint-staged -c "
},
"dependencies": {
"@annotorious/annotorious": "^3.7.11",
"@annotorious/core": "^3.7.8",
"@annotorious/openseadragon": "^3.7.11",
"@element-plus/icons-vue": "^2.1.0",
"@form-create/designer": "^3.1.3",
"@form-create/element-ui": "^3.1.24",
"@iconify/iconify": "^3.1.1",
"@recogito/annotorious": "^2.7.13",
"@videojs-player/vue": "^1.0.0",
"@vueuse/core": "^10.9.0",
"@wangeditor/editor": "^5.1.23",
@ -55,6 +59,7 @@
"min-dash": "^4.1.1",
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"openseadragon": "^5.0.1",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"qrcode": "^1.5.3",

@ -0,0 +1,46 @@
import request from '@/config/axios'
// 数据集管理 VO
export interface DatasVO {
id: number // id
name: string // 项目名称
description: string // 描述
status: string // 类型,还未标注,正在标注,正在训练,训练完成
path: string // 路径
count: number // 图片总数
type: string // 识别类型
progress: number // 进度
}
// 数据集管理 API
export const DatasApi = {
// 查询数据集管理分页
getDatasPage: async (params: any) => {
return await request.get({ url: `/annotation/datas/page`, params })
},
// 查询数据集管理详情
getDatas: async (id: number) => {
return await request.get({ url: `/annotation/datas/get?id=` + id })
},
// 新增数据集管理
createDatas: async (data: DatasVO) => {
return await request.post({ url: `/annotation/datas/create`, data })
},
// 修改数据集管理
updateDatas: async (data: DatasVO) => {
return await request.put({ url: `/annotation/datas/update`, data })
},
// 删除数据集管理
deleteDatas: async (id: number) => {
return await request.delete({ url: `/annotation/datas/delete?id=` + id })
},
// 导出数据集管理 Excel
exportDatas: async (params) => {
return await request.download({ url: `/annotation/datas/export-excel`, params })
},
}

@ -0,0 +1,129 @@
import request from '@/config/axios'
// 标注 VO
export interface MarkVO {
}
// 项目数据 VO
export interface DataVO {
id: number
name: string
description: string
createTime: string
path: string
count: number
type: string
progress: number
}
// 图片标注 VO
export interface ImageMarkVO {
id: number
path: string
dataId: number
annotation: Array<{
class_id: number
center_x: number
center_y: number
width: number
height: number
polygon_points: string
angle: string
}>
status: number
createTime: string
}
// 标注类型 VO
export interface AnnotationTypeVO {
id: number
name: string
color: string
dataId: number
index: number
createTime: string
}
// 标注 API
export const MarkApi = {
// 查询标注分页
getMarkPage: async (params: any) => {
return await request.get({ url: `/annotation/mark/page`, params })
},
// 查询标注详情
getMark: async (id: number) => {
return await request.get({ url: `/annotation/mark/get?id=` + id })
},
// 新增标注
createMark: async (data: MarkVO) => {
return await request.post({ url: `/annotation/mark/create`, data })
},
// 修改标注
updateMark: async (data: MarkVO) => {
return await request.put({ url: `/annotation/mark/update`, data })
},
// 删除标注
deleteMark: async (id: number) => {
return await request.delete({ url: `/annotation/mark/delete?id=` + id })
},
// 导出标注 Excel
exportMark: async (params) => {
return await request.download({ url: `/annotation/mark/export-excel`, params })
},
// 获取图片列表
getImageList: async (data: { dataId: number }) => {
return await request.post({ url: `/annotation/mark/list`, data })
},
// 获取标注类型列表
getTypeList: async (data: { dataId: number }) => {
return await request.post({ url: `/annotation/types/list`, data })
},
updateImageStatus: async (data: { id: number; status: number }) => {
return await request.put({ url: `/annotation/mark/update-status`, data })
},
// 获取标注类型列表
getProjectList: async () => {
return await request.post({ url: `/annotation/datas/list` })
},
// 创建标注类型
createType: async (data: Partial<AnnotationTypeVO>) => {
return await request.post({ url: `/annotation/types/create`, data })
},
// 删除标注类型
deleteType: async (id: number) => {
return await request.delete({ url: `/annotation/types/delete?id=${id}` })
},
// 获取标注详细列表
getMarkInfoList: async (id: number) => {
return await request.post({ url: `/annotation/markInfo/list?markId=${id}` })
},
// 创建标注类型
createMarkInfo: async (data ) => {
return await request.post({ url: `/annotation/markInfo/create`, data })
},
// 删除标注类型
deleteMarkInfo: async (id: number) => {
return await request.delete({ url: `/annotation/markInfo/delete?id=${id}` })
},
// 获取标注详细列表
updateMarkInfo: async () => {
return await request.put({ url: `/annotation/markInfo/update` })
},
}

@ -0,0 +1,49 @@
import request from '@/config/axios'
// 训练 VO
export interface TrainVO {
id: number // id
dataId: number // 项目id
train: number // 训练集比例
val: number // 验证图像比例
test: number // 测试图像比例
round: number // 轮次
size: number // 批次大小
imageSize: number // 图片大小(正方向,大于这个值进行缩放,小于这个值进行放大,不是正方形将图片周围涂黑)
modelPath: string // 预选训练模型
path: string // 训练图片路径
trainType: number // 类型使用哪个gpu
}
// 训练 API
export const TrainApi = {
// 查询训练分页
getTrainPage: async (params: any) => {
return await request.get({ url: `/annotation/train/page`, params })
},
// 查询训练详情
getTrain: async (id: number) => {
return await request.get({ url: `/annotation/train/get?id=` + id })
},
// 新增训练
createTrain: async (data: TrainVO) => {
return await request.post({ url: `/annotation/train/create`, data })
},
// 修改训练
updateTrain: async (data: TrainVO) => {
return await request.put({ url: `/annotation/train/update`, data })
},
// 删除训练
deleteTrain: async (id: number) => {
return await request.delete({ url: `/annotation/train/delete?id=` + id })
},
// 导出训练 Excel
exportTrain: async (params) => {
return await request.download({ url: `/annotation/train/export-excel`, params })
},
}

@ -0,0 +1,38 @@
import request from '@/config/axios'
// 识别结果 VO
export interface TrainResultVO {
}
// 识别结果 API
export const TrainResultApi = {
// 查询识别结果分页
getTrainResultPage: async (params: any) => {
return await request.get({ url: `/annotation/train-result/page`, params })
},
// 查询识别结果详情
getTrainResult: async (id: number) => {
return await request.get({ url: `/annotation/train-result/get?id=` + id })
},
// 新增识别结果
createTrainResult: async (data: TrainResultVO) => {
return await request.post({ url: `/annotation/train-result/create`, data })
},
// 修改识别结果
updateTrainResult: async (data: TrainResultVO) => {
return await request.put({ url: `/annotation/train-result/update`, data })
},
// 删除识别结果
deleteTrainResult: async (id: number) => {
return await request.delete({ url: `/annotation/train-result/delete?id=` + id })
},
// 导出识别结果 Excel
exportTrainResult: async (params) => {
return await request.download({ url: `/annotation/train-result/export-excel`, params })
},
}

@ -0,0 +1,43 @@
import request from '@/config/axios'
// 类别 VO
export interface TypesVO {
id: number // id
name: string // 名称
dataId: number // 项目id
index: number // index
color: string // 颜色
}
// 类别 API
export const TypesApi = {
// 查询类别分页
getTypesPage: async (params: any) => {
return await request.get({ url: `/annotation/types/page`, params })
},
// 查询类别详情
getTypes: async (id: number) => {
return await request.get({ url: `/annotation/types/get?id=` + id })
},
// 新增类别
createTypes: async (data: TypesVO) => {
return await request.post({ url: `/annotation/types/create`, data })
},
// 修改类别
updateTypes: async (data: TypesVO) => {
return await request.put({ url: `/annotation/types/update`, data })
},
// 删除类别
deleteTypes: async (id: number) => {
return await request.delete({ url: `/annotation/types/delete?id=` + id })
},
// 导出类别 Excel
exportTypes: async (params) => {
return await request.download({ url: `/annotation/types/export-excel`, params })
},
}

@ -0,0 +1,39 @@
import request from '@/config/axios'
// 报警信息 VO
export interface WarnVO {
uuid: string // uuid
}
// 报警信息 API
export const WarnApi = {
// 查询报警信息分页
getWarnPage: async (params: any) => {
return await request.get({ url: `/logistics/warn/page`, params })
},
// 查询报警信息详情
getWarn: async (id: number) => {
return await request.get({ url: `/logistics/warn/get?id=` + id })
},
// 新增报警信息
createWarn: async (data: WarnVO) => {
return await request.post({ url: `/logistics/warn/create`, data })
},
// 修改报警信息
updateWarn: async (data: WarnVO) => {
return await request.put({ url: `/logistics/warn/update`, data })
},
// 删除报警信息
deleteWarn: async (id: number) => {
return await request.delete({ url: `/logistics/warn/delete?id=` + id })
},
// 导出报警信息 Excel
exportWarn: async (params) => {
return await request.download({ url: `/logistics/warn/export-excel`, params })
},
}

@ -0,0 +1,46 @@
import request from '@/config/axios'
// 品规配置 VO
export interface SpecificationConfVO {
width: number // 宽度
length: number // 长度
height: number // 整堆高度
minArea: number // 最小面积
tolerance: number // 截取高度
name: string // 名称
type: string // 类型
high: number // 高度
}
// 品规配置 API
export const SpecificationConfApi = {
// 查询品规配置分页
getSpecificationConfPage: async (params: any) => {
return await request.get({ url: `/logistics/specification-conf/page`, params })
},
// 查询品规配置详情
getSpecificationConf: async (id: number) => {
return await request.get({ url: `/logistics/specification-conf/get?id=` + id })
},
// 新增品规配置
createSpecificationConf: async (data: SpecificationConfVO) => {
return await request.post({ url: `/logistics/specification-conf/create`, data })
},
// 修改品规配置
updateSpecificationConf: async (data: SpecificationConfVO) => {
return await request.put({ url: `/logistics/specification-conf/update`, data })
},
// 删除品规配置
deleteSpecificationConf: async (id: number) => {
return await request.delete({ url: `/logistics/specification-conf/delete?id=` + id })
},
// 导出品规配置 Excel
exportSpecificationConf: async (params) => {
return await request.download({ url: `/logistics/specification-conf/export-excel`, params })
},
}

@ -64,7 +64,7 @@
* */
import { resetSize } from './../utils/util'
import { aesEncrypt } from './../utils/ase'
import { getCode, reqCheck } from '@/api/login'
import { reqCheck } from '@/api/login'
import { getCurrentInstance, nextTick, onMounted, reactive, ref, toRefs } from 'vue'
const props = defineProps({
@ -227,16 +227,7 @@ const getPictrue = async () => {
let data = {
captchaType: captchaType.value
}
const res = await getCode(data)
if (res.repCode == '0000') {
pointBackImgBase.value = res.repData.originalImageBase64
backToken.value = res.repData.token
secretKey.value = res.repData.secretKey
poinTextList.value = res.repData.wordList
text.value = t('captcha.point') + '【' + poinTextList.value.join(',') + '】'
} else {
text.value = res.repMsg
}
//
}
//
const pointTransfrom = function (pointArr, imgSize) {

@ -8222,4 +8222,4 @@ var ZLMRTCClient = (function (exports) {
return exports;
})({});
//# sourceMappingURL=ZLMRTCClient.js.map

@ -221,7 +221,7 @@ export default {
const refresh = () => {
console.log("刷新");
// 使 $emit
emit('childTriggerParent')
emit('childTriggerParent',props.cameraId)
}
const activeIcons = {
zoomSub: 'ep:zoom-out-active',
@ -272,6 +272,7 @@ export default {
upIcon,
rightUpIcon,
leftIcon,
refresh,
centerIcon,
rightIcon,
leftDownIcon,

@ -119,6 +119,9 @@ export enum DICT_TYPE {
TERMINAL = 'terminal', // 终端
DATE_INTERVAL = 'date_interval', // 数据间隔
// ========== annotation 视觉模块 ==========
VISUAL_TYPE = 'visual_type',
TRAINING_STATUS = 'training_status',
// ========== SYSTEM 模块 ==========
SYSTEM_USER_SEX = 'system_user_sex',
SYSTEM_MENU_TYPE = 'system_menu_type',

@ -1,319 +0,0 @@
<template>
<el-row :class="prefixCls" :gutter="20" justify="space-between">
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" :rows="2" animated>
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div>
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--peoples p-16px inline-block rounded-6px`"
>
<Icon :size="40" icon="svg-icon:peoples" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
>{{ t('analysis.newUser') }}
</div>
<CountTo
:duration="2600"
:end-val="102400"
:start-val="0"
class="text-right text-20px font-700"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" :rows="2" animated>
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div>
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--message p-16px inline-block rounded-6px`"
>
<Icon :size="40" icon="svg-icon:message" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
>{{ t('analysis.unreadInformation') }}
</div>
<CountTo
:duration="2600"
:end-val="81212"
:start-val="0"
class="text-right text-20px font-700"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" :rows="2" animated>
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div>
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
>
<Icon :size="40" icon="svg-icon:money" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
>{{ t('analysis.transactionAmount') }}
</div>
<CountTo
:duration="2600"
:end-val="9280"
:start-val="0"
class="text-right text-20px font-700"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
<el-col :lg="6" :md="12" :sm="12" :xl="6" :xs="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" :rows="2" animated>
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div>
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--shopping p-16px inline-block rounded-6px`"
>
<Icon :size="40" icon="svg-icon:shopping" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`"
>{{ t('analysis.totalShopping') }}
</div>
<CountTo
:duration="2600"
:end-val="13600"
:start-val="0"
class="text-right text-20px font-700"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" justify="space-between">
<el-col :lg="10" :md="24" :sm="24" :xl="10" :xs="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" animated>
<Echart :height="300" :options="pieOptionsData" />
</el-skeleton>
</el-card>
</el-col>
<el-col :lg="14" :md="24" :sm="24" :xl="14" :xs="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" animated>
<Echart :height="300" :options="barOptionsData" />
</el-skeleton>
</el-card>
</el-col>
<el-col :span="24">
<el-card class="mb-20px" shadow="hover">
<el-skeleton :loading="loading" :rows="4" animated>
<Echart :height="350" :options="lineOptionsData" />
</el-skeleton>
</el-card>
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { set } from 'lodash-es'
import { EChartsOption } from 'echarts'
import { useDesign } from '@/hooks/web/useDesign'
import type { AnalysisTotalTypes } from './types'
import { barOptions, lineOptions, pieOptions } from './echarts-data'
defineOptions({ name: 'Home2' })
const { t } = useI18n()
const loading = ref(true)
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('panel')
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
let totalState = reactive<AnalysisTotalTypes>({
users: 0,
messages: 0,
moneys: 0,
shoppings: 0
})
const getCount = async () => {
const data = {
users: 102400,
messages: 81212,
moneys: 9280,
shoppings: 13600
}
totalState = Object.assign(totalState, data)
}
//
const getUserAccessSource = async () => {
const data = [
{ value: 335, name: 'analysis.directAccess' },
{ value: 310, name: 'analysis.mailMarketing' },
{ value: 234, name: 'analysis.allianceAdvertising' },
{ value: 135, name: 'analysis.videoAdvertising' },
{ value: 1548, name: 'analysis.searchEngines' }
]
set(
pieOptionsData,
'legend.data',
data.map((v) => t(v.name))
)
set(pieOptionsData, 'series.data', data)
}
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
//
const getWeeklyUserActivity = async () => {
const data = [
{ value: 13253, name: 'analysis.monday' },
{ value: 34235, name: 'analysis.tuesday' },
{ value: 26321, name: 'analysis.wednesday' },
{ value: 12340, name: 'analysis.thursday' },
{ value: 24643, name: 'analysis.friday' },
{ value: 1322, name: 'analysis.saturday' },
{ value: 1324, name: 'analysis.sunday' }
]
set(
barOptionsData,
'xAxis.data',
data.map((v) => t(v.name))
)
set(barOptionsData, 'series', [
{
name: t('analysis.activeQuantity'),
data: data.map((v) => v.value),
type: 'bar'
}
])
}
const lineOptionsData = reactive<EChartsOption>(lineOptions) as EChartsOption
//
const getMonthlySales = async () => {
const data = [
{ estimate: 100, actual: 120, name: 'analysis.january' },
{ estimate: 120, actual: 82, name: 'analysis.february' },
{ estimate: 161, actual: 91, name: 'analysis.march' },
{ estimate: 134, actual: 154, name: 'analysis.april' },
{ estimate: 105, actual: 162, name: 'analysis.may' },
{ estimate: 160, actual: 140, name: 'analysis.june' },
{ estimate: 165, actual: 145, name: 'analysis.july' },
{ estimate: 114, actual: 250, name: 'analysis.august' },
{ estimate: 163, actual: 134, name: 'analysis.september' },
{ estimate: 185, actual: 56, name: 'analysis.october' },
{ estimate: 118, actual: 99, name: 'analysis.november' },
{ estimate: 123, actual: 123, name: 'analysis.december' }
]
set(
lineOptionsData,
'xAxis.data',
data.map((v) => t(v.name))
)
set(lineOptionsData, 'series', [
{
name: t('analysis.estimate'),
smooth: true,
type: 'line',
data: data.map((v) => v.estimate),
animationDuration: 2800,
animationEasing: 'cubicInOut'
},
{
name: t('analysis.actual'),
smooth: true,
type: 'line',
itemStyle: {},
data: data.map((v) => v.actual),
animationDuration: 2800,
animationEasing: 'quadraticOut'
}
])
}
const getAllApi = async () => {
await Promise.all([getCount(), getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()])
loading.value = false
}
getAllApi()
</script>
<style lang="scss" scoped>
$prefix-cls: #{$namespace}-panel;
.#{$prefix-cls} {
&__item {
&--peoples {
color: #40c9c6;
}
&--message {
color: #36a3f7;
}
&--money {
color: #f4516c;
}
&--shopping {
color: #34bfa3;
}
&:hover {
:deep(.#{$namespace}-icon) {
color: #fff !important;
}
.#{$prefix-cls}__item--icon {
transition: all 0.38s ease-out;
}
.#{$prefix-cls}__item--peoples {
background: #40c9c6;
}
.#{$prefix-cls}__item--message {
background: #36a3f7;
}
.#{$prefix-cls}__item--money {
background: #f4516c;
}
.#{$prefix-cls}__item--shopping {
background: #34bfa3;
}
}
}
}
</style>

@ -1,383 +0,0 @@
<template>
<!-- <div>
<el-card shadow="never">
<el-skeleton :loading="loading" animated>
<el-row :gutter="16" justify="space-between">
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex items-center">
<el-avatar :src="avatar" :size="70" class="mr-16px">
<img src="@/assets/imgs/avatar.gif" alt="" />
</el-avatar>
<div>
<div class="text-20px">
{{ t('workplace.welcome') }} {{ username }} {{ t('workplace.happyDay') }}
</div>
<div class="mt-10px text-14px text-gray-500">
{{ t('workplace.toady') }}20 - 32
</div>
</div>
</div>
</el-col>
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="h-70px flex items-center justify-end lt-sm:mt-10px">
<div class="px-8px text-right">
<div class="mb-16px text-14px text-gray-400">{{ t('workplace.project') }}</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.project"
:duration="2600"
/>
</div>
<el-divider direction="vertical" />
<div class="px-8px text-right">
<div class="mb-16px text-14px text-gray-400">{{ t('workplace.toDo') }}</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.todo"
:duration="2600"
/>
</div>
<el-divider direction="vertical" border-style="dashed" />
<div class="px-8px text-right">
<div class="mb-16px text-14px text-gray-400">{{ t('workplace.access') }}</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.access"
:duration="2600"
/>
</div>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
</div> -->
<el-row class="mt-8px" :gutter="8" justify="space-between">
<!-- <el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-8px">
<el-card shadow="never">
<template #header>
<div class="h-3 flex justify-between">
<span>{{ t('workplace.project') }}</span>
<el-link
type="primary"
:underline="false"
href="https://github.com/yudaocode"
target="_blank"
>
{{ t('action.more') }}
</el-link>
</div>
</template>
<el-skeleton :loading="loading" animated>
<el-row>
<el-col
v-for="(item, index) in projects"
:key="`card-${index}`"
:xl="8"
:lg="8"
:md="8"
:sm="24"
:xs="24"
>
<el-card shadow="hover" class="mr-5px mt-5px">
<div class="flex items-center">
<Icon :icon="item.icon" :size="25" class="mr-8px" />
<span class="text-16px">{{ item.name }}</span>
</div>
<div class="mt-12px text-9px text-gray-400">{{ t(item.message) }}</div>
<div class="mt-12px flex justify-between text-12px text-gray-400">
<span>{{ item.personal }}</span>
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
</div>
</el-card>
</el-col>
</el-row>
</el-skeleton>
</el-card>
<el-card shadow="never" class="mt-8px">
<el-skeleton :loading="loading" animated>
<el-row :gutter="20" justify="space-between">
<el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
<el-card shadow="hover" class="mb-8px">
<el-skeleton :loading="loading" animated>
<Echart :options="pieOptionsData" :height="280" />
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
<el-card shadow="hover" class="mb-8px">
<el-skeleton :loading="loading" animated>
<Echart :options="barOptionsData" :height="280" />
</el-skeleton>
</el-card>
</el-col>
</el-row>
</el-skeleton>
</el-card>
</el-col> -->
<el-col :xl="8" :lg="24" :md="24" :sm="24" :xs="24" class="mb-16px">
<el-card shadow="never">
<template #header>
<div class="h-3 flex justify-between">
<span>{{ t('workplace.shortcutOperation') }}</span>
</div>
</template>
<el-skeleton :loading="loading" animated>
<el-row>
<el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-40px">
<div class="flex items-center">
<Icon :icon="item.icon" size="70" class="mr-10px" />
<router-link :to="item.url">
<el-link type="default" :underline="false" >
{{ item.name }}
</el-link>
</router-link>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
<!-- <el-card shadow="never" class="mt-8px">
<template #header>
<div class="h-3 flex justify-between">
<span>{{ t('workplace.notice') }}</span>
<el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
</div>
</template>
<el-skeleton :loading="loading" animated>
<div v-for="(item, index) in notice" :key="`dynamics-${index}`">
<div class="flex items-center">
<el-avatar :src="avatar" :size="35" class="mr-16px">
<img src="@/assets/imgs/avatar.gif" alt="" />
</el-avatar>
<div>
<div class="text-14px">
<Highlight :keys="item.keys.map((v) => t(v))">
{{ item.type }} : {{ item.title }}
</Highlight>
</div>
<div class="mt-16px text-12px text-gray-400">
{{ formatTime(item.date, 'yyyy-MM-dd') }}
</div>
</div>
</div>
<el-divider />
</div>
</el-skeleton>
</el-card> -->
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { set } from 'lodash-es'
import { EChartsOption } from 'echarts'
import { formatTime } from '@/utils'
import { useUserStore } from '@/store/modules/user'
import { useWatermark } from '@/hooks/web/useWatermark'
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
import { pieOptions, barOptions } from './echarts-data'
defineOptions({ name: 'Home' })
const { t } = useI18n()
const userStore = useUserStore()
const { setWatermark } = useWatermark()
const loading = ref(true)
const avatar = userStore.getUser.avatar
const username = userStore.getUser.nickname
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
//
let totalSate = reactive<WorkplaceTotal>({
project: 0,
access: 0,
todo: 0
})
const getCount = async () => {
const data = {
project: 40,
access: 2340,
todo: 10
}
totalSate = Object.assign(totalSate, data)
}
//
let projects = reactive<Project[]>([])
const getProject = async () => {
const data = [
{
name: 'ruoyi-vue-pro',
icon: 'akar-icons:github-fill',
message: 'https://github.com/YunaiV/ruoyi-vue-pro',
personal: 'Spring Boot 单体架构',
time: new Date()
},
{
name: 'yudao-ui-admin-vue3',
icon: 'logos:vue',
message: 'https://github.com/yudaocode/yudao-ui-admin-vue3',
personal: 'Vue3 + element-plus',
time: new Date()
},
{
name: 'yudao-ui-admin-vben',
icon: 'logos:vue',
message: 'https://github.com/yudaocode/yudao-ui-admin-vben',
personal: 'Vue3 + vben(antd)',
time: new Date()
},
{
name: 'yudao-cloud',
icon: 'akar-icons:github',
message: 'https://github.com/YunaiV/yudao-cloud',
personal: 'Spring Cloud 微服务架构',
time: new Date()
},
{
name: 'yudao-ui-mall-uniapp',
icon: 'logos:vue',
message: 'https://github.com/yudaocode/yudao-ui-admin-uniapp',
personal: 'Vue3 + uniapp',
time: new Date()
},
{
name: 'yudao-ui-admin-vue2',
icon: 'logos:vue',
message: 'https://github.com/yudaocode/yudao-ui-admin-vue2',
personal: 'Vue2 + element-ui',
time: new Date()
}
]
projects = Object.assign(projects, data)
}
//
let notice = reactive<Notice[]>([])
const getNotice = async () => {
const data = [
{
title: '系统支持 JDK 8/17/21Vue 2/3',
type: '通知',
keys: ['通知', '8', '17', '21', '2', '3'],
date: new Date()
},
{
title: '后端提供 Spring Boot 2.7/3.2 + Cloud 双架构',
type: '公告',
keys: ['公告', 'Boot', 'Cloud'],
date: new Date()
},
{
title: '全部开源,个人与企业可 100% 直接使用,无需授权',
type: '通知',
keys: ['通知', '无需授权'],
date: new Date()
},
{
title: '国内使用最广泛的快速开发平台,超 300+ 人贡献',
type: '公告',
keys: ['公告', '最广泛'],
date: new Date()
}
]
notice = Object.assign(notice, data)
}
//
let shortcut = reactive<Shortcut[]>([])
const getShortcut = async () => {
const data = [
{
name: '随行记录',
icon: 'ep:data-analysis',
url: 'github.io'
},
{
name: '盘点记录',
icon: 'ep:coin',
url: 'ep:coin'
},
{
name: '盘点管理',
icon: 'ep:pointer',
url: 'ep:coin'
},
{
name: '视频墙',
icon: 'ep:data-board',
url: 'github.io'
},
{
name: '实时视频',
icon: 'ep:copy-document',
url: 'github.io'
},
{
name: '相机管理',
icon: 'ep:camera-filled',
url: 'github.io'
}
]
shortcut = Object.assign(shortcut, data)
}
//
const getUserAccessSource = async () => {
const data = [
{ value: 335, name: 'analysis.directAccess' },
{ value: 310, name: 'analysis.mailMarketing' },
{ value: 234, name: 'analysis.allianceAdvertising' },
{ value: 135, name: 'analysis.videoAdvertising' },
{ value: 1548, name: 'analysis.searchEngines' }
]
set(
pieOptionsData,
'legend.data',
data.map((v) => t(v.name))
)
pieOptionsData!.series![0].data = data.map((v) => {
return {
name: t(v.name),
value: v.value
}
})
}
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
const getAllApi = async () => {
await Promise.all([
getCount(),
getProject(),
getNotice(),
getShortcut(),
getUserAccessSource(),
])
loading.value = false
}
getAllApi()
</script>
<style>
.text-success {
color: green;
}
.text-danger {
color: red;
}
</style>

@ -33,7 +33,7 @@
:prefix-icon="iconLock"
show-password
type="password"
@keyup.enter="getCode()"
/>
</el-form-item>
</el-col>

@ -0,0 +1,125 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="项目名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入项目名称" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" placeholder="描述" />
<!-- <Editor v-model="formData.description" height="150px" /> -->
</el-form-item>
<el-form-item label="路径" prop="path">
<el-input v-model="formData.path" placeholder="请输入路径" />
</el-form-item>
<el-form-item label="识别类型" prop="type">
<el-select v-model="formData.type" placeholder="请选择识别类型">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.VISUAL_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DatasApi, DatasVO } from '@/api/annotation/datas'
import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
// import Editor from '@/components/Editor/src/Editor.vue'
/** 数据集管理 表单 */
defineOptions({ name: 'DatasForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
name: undefined,
description: undefined,
status: undefined,
path: undefined,
count: undefined,
type: undefined,
progress: undefined,
})
const formRules = reactive({
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await DatasApi.getDatas(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as DatasVO
if (formType.value === 'create') {
await DatasApi.createDatas(data)
message.success(t('common.createSuccess'))
} else {
await DatasApi.updateDatas(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
description: undefined,
status: undefined,
path: undefined,
count: undefined,
type: undefined,
progress: undefined,
}
formRef.value?.resetFields()
}
</script>

@ -0,0 +1,283 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="项目名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入项目名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="类型" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择类型"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.TRAINING_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="识别类型" prop="type">
<el-select
v-model="queryParams.type"
placeholder="请选择识别类型"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.VISUAL_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['annotation:datas:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['annotation:datas:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="项目名称" align="center" prop="name" />
<el-table-column label="描述" align="center" prop="description" />
<!-- 还未标注正在标注正在训练训练完成 -->
<el-table-column label="类型" align="center" prop="status" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.TRAINING_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="路径" align="center" prop="path" />
<el-table-column label="图片总数" align="center" prop="count" />
<el-table-column label="识别类型" align="center" prop="type" >
<template #default="scope">
<dict-tag :type="DICT_TYPE.VISUAL_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="进度" align="center" prop="progress" >
<template #default="scope">
{{ scope.row.progress +"%" }}
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column
width="400px" label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['annotation:datas:update']"
>
编辑
</el-button>
<el-button
link
type="primary"
@click="handleDelete(scope.row.id)"
v-hasPermi="['annotation:datas:delete']"
>
刷新
</el-button>
<el-button
link
type="primary"
@click="handleDelete(scope.row.id)"
v-hasPermi="['annotation:datas:delete']"
>
增强
</el-button>
<router-link :to="'/dict/type/data/' + scope.row.id">
<el-button link type="primary">标注</el-button>
</router-link>
<router-link :to="'/dict/type/data/' + scope.row.id">
<el-button link type="primary">训练</el-button>
</router-link>
<router-link :to="'/dict/type/data/' + scope.row.id">
<el-button link type="primary">测试</el-button>
</router-link>
<el-button
link
type="primary"
@click="handleDelete(scope.row.id)"
v-hasPermi="['annotation:datas:delete']"
>
导出
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['annotation:datas:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<DatasForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { DatasApi, DatasVO } from '@/api/annotation/datas'
// import DatasForm from './DatasForm.vue'
import DatasForm from './DatasForm.vue'
/** 数据集管理 列表 */
defineOptions({ name: 'Datas' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<DatasVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
description: undefined,
status: undefined,
path: undefined,
count: undefined,
type: undefined,
progress: undefined,
createTime: [],
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await DatasApi.getDatasPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await DatasApi.deleteDatas(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await DatasApi.exportDatas(queryParams)
download.excel(data, '数据集管理.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

@ -0,0 +1,84 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<!-- <el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
</el-form> -->
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { MarkApi, MarkVO } from '@/api/annotation/mark'
/** 标注 表单 */
defineOptions({ name: 'MarkForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
})
const formRules = reactive({
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await MarkApi.getMark(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as MarkVO
if (formType.value === 'create') {
await MarkApi.createMark(data)
message.success(t('common.createSuccess'))
} else {
await MarkApi.updateMark(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
}
formRef.value?.resetFields()
}
</script>

@ -0,0 +1,992 @@
<template>
<div class="mark-container">
<!-- 左侧区域 -->
<el-aside class="left-container" width="300px">
<br/>
<el-select
v-model="selectedProjectId"
placeholder="请选择项目"
@change="handleProjectChange"
style="width: 100%"
>
<el-option
v-for="project in projectList"
:key="project.id"
:label="project.name"
:value="project.id"
/>
</el-select>
<el-collapse v-model="activeLeftPanels" accordion>
<!-- 项目信息面板 -->
<el-collapse-item title="项目信息" name="projectInfo">
<div v-if="currentProject">
<el-descriptions :column="1" size="small">
<el-descriptions-item label="项目名称">{{ currentProject.name }}</el-descriptions-item>
<el-descriptions-item label="项目描述">{{ currentProject.description }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ formatTimestamp(currentProject.createTime)}}</el-descriptions-item>
</el-descriptions>
</div>
<div v-else>
<el-empty description="请选择项目" :image-size="60" />
</div>
</el-collapse-item>
<!-- 标注类型面板 -->
<el-collapse-item title="标注类型" name="types">
<div class="type-header">
<el-button type="primary" size="small" @click="showAddTypeDialog">
<Icon icon="ep:plus" /> 新增
</el-button>
</div>
<el-scrollbar class="left-scrollbar">
<el-table :data="annotationTypes" style="width: 100%">
<el-table-column label="类型" prop="name" />
<el-table-column label="颜色" width="80">
<template #default="scope">
<div class="color-circle" :style="{ backgroundColor: scope.row.color }"></div>
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="scope">
<el-button link type="danger" @click="deleteAnnotationType(scope.row.id)"></el-button>
</template>
</el-table-column>
</el-table>
</el-scrollbar>
</el-collapse-item>
<!-- 标注结果面板 -->
<!-- 修改标注结果面板 -->
<el-collapse-item title="标注结果" name="results">
<el-scrollbar class="left-scrollbar">
<el-table :data="annotations" style="width: 100%" @row-click="selectAnnotation">
<el-table-column label="类型设置" width="120">
<template #default="scope">
<el-select
v-model="scope.row.classId"
placeholder="选择类型"
size="small"
@change="(value) => setAnnotationType(scope.$index, value)"
>
<el-option
v-for="type in annotationTypes"
:key="type.id"
:label="type.name"
:value="type.id"
/>
</el-select>
</template>
</el-table-column>
<!-- 修改标注结果面板中的属性引用 -->
<el-table-column label="坐标信息" prop="coordinates">
<template #default="scope">
<div class="coordinates-info">
<div v-if="scope.row.centerX !== undefined">
中心: ({{ Math.round(scope.row.centerX) }}, {{ Math.round(scope.row.centerY) }})
</div>
<div v-if="scope.row.width !== undefined">
尺寸: {{ Math.round(scope.row.width) }} x {{ Math.round(scope.row.height) }}
</div>
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="scope">
<el-button link type="danger" @click="deleteAnnotation(scope.row, scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-scrollbar>
</el-collapse-item>
</el-collapse>
</el-aside>
<!-- 中间区域图片标注区域 -->
<el-main class="center-container">
<div ref="annotatorContainer" class="annotator-wrapper">
<img
v-if="currentImage"
:src="currentImage.path"
:alt="currentImage.name"
class="annotator-image"
ref="annotatorImage"
/>
<el-empty v-else description="请选择图片进行标注" />
</div>
</el-main>
<!-- 右侧区域 -->
<el-aside class="right-container" width="300px">
<el-collapse v-model="activeRightPanels" accordion>
<!-- 图片选择面板 -->
<el-collapse-item title="图片选择" name="images">
<el-scrollbar class="image-scrollbar">
<div class="image-grid">
<div
v-for="image in imageList"
:key="image.id"
class="image-item"
:class="{ active: currentImage && currentImage.id === image.id }"
@click="selectImage(image)"
>
<div class="image-wrapper">
<img
:src="image.path"
:alt="image.name"
class="thumbnail"
/>
<!-- 添加状态指示器 -->
<div v-if="image.status === 1" class="status-indicator">
<Icon icon="ep:check" />
</div>
</div>
</div>
</div>
</el-scrollbar>
</el-collapse-item>
</el-collapse>
</el-aside>
</div>
<!-- 添加类型对话框 -->
<el-dialog v-model="addTypeDialogVisible" title="添加标注类型" width="400px">
<el-form :model="newTypeForm" ref="typeFormRef" label-width="80px">
<el-form-item label="类型名称" prop="name" :rules="[{ required: true, message: '请输入类型名称', trigger: 'blur' }]">
<el-input v-model="newTypeForm.name" placeholder="请输入类型名称" />
</el-form-item>
<el-form-item label="颜色" prop="color" :rules="[{ required: true, message: '请选择颜色', trigger: 'blur' }]">
<el-color-picker v-model="newTypeForm.color" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="addTypeDialogVisible = false">取消</el-button>
<el-button type="primary" @click="addAnnotationType"></el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { MarkApi } from '@/api/annotation/mark'
import { createImageAnnotator } from '@annotorious/annotorious';
// ID
const defaultTypeId = ref<number | null>(null);
// Import essential CSS styles
import '@annotorious/annotorious/annotorious.css';
// script setup
const usedColors: string[] = [];
//
let annoInstance = null
// annotorious
const initAnnotorious = async () => {
await nextTick();
if (currentImage.value && annotatorImage.value) {
//
if (annoInstance) {
annoInstance.destroy();
}
// 使 Annotorious 3 API
annoInstance = createImageAnnotator(annotatorImage.value, {
locale: 'auto',
theme: 'dark'
});
//
annoInstance.on('createAnnotation', handleAnnotationCreated);
annoInstance.on('updateAnnotation', handleAnnotationUpdated);
annoInstance.on('deleteAnnotation', handleAnnotationDeleted);
annoInstance.on('clickAnnotation', handleClickAnnotation);
annoInstance.on('selectionChanged', handleSelectionChanged);
//
loadExistingAnnotations();
}
};
//
const deleteAnnotation = async (annotation: any, index: number) => {
try {
// Annotorious
if (annoInstance && annotation.annotationData) {
annoInstance.removeAnnotation(annotation.annotationData);
}
//
annotations.value.splice(index, 1);
// API
// await MarkApi.deleteMark(annotation.id);
console.log('删除成功');
} catch (error) {
console.error('删除标注失败:', error);
alert('删除失败');
}
};
// HSL
const generateDistinctColor = () => {
//
const hueSteps = 50;
const saturation = Math.floor(Math.random() * 40) + 60; // 60-100%
const lightness = Math.floor(Math.random() * 30) + 40; // 40-70%
// 使50使
if (usedColors.length < 50) {
const hue = Math.floor((usedColors.length * 360) / hueSteps) % 360;
const color = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
usedColors.push(color);
return color;
} else {
// 50使
const hue = Math.floor(Math.random() * 360);
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
}
}
// HSLHEX
const hslToHex = (h: number, s: number, l: number) => {
l /= 100;
const a = s * Math.min(l, 1 - l) / 100;
const f = n => {
const k = (n + h / 30) % 12;
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
return Math.round(255 * color).toString(16).padStart(2, '0');
};
return `#${f(0)}${f(8)}${f(4)}`;
}
// generateRandomColor
const generateRandomColor = () => {
//
const baseColors = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
'#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9',
'#F8C471', '#82E0AA', '#F1948A', '#85C1E9', '#D7BDE2',
'#A3E4D7', '#FAD7A0', '#D5A6BD', '#AED6F1', '#A9DFBF',
'#F9E79F', '#D2B4DE', '#AED6F1', '#A2D9CE', '#FADBD8',
'#EBDEF0', '#D6EAF8', '#D1F2EB', '#FCF3CF', '#FDEDEC',
'#52BE80', '#F4D03F', '#E67E22', '#D35400', '#8E44AD',
'#2980B9', '#1ABC9C', '#F39C12', '#D35400', '#C0392B',
'#16A085', '#27AE60', '#F1C40F', '#E67E22', '#9B59B6',
'#3498DB', '#2ECC71', '#F1C40F', '#E74C3C', '#9B59B6'
];
//
const existingColors = annotationTypes.value.map(type => type.color);
//
const availableColors = baseColors.filter(color => !existingColors.includes(color));
// 使50使
if (usedColors.length < 50 && availableColors.length > 0) {
//
const color = availableColors[usedColors.length % availableColors.length];
usedColors.push(color);
return color;
} else if (usedColors.length < 50 && availableColors.length === 0 && existingColors.length < baseColors.length) {
// 使使
const hueSteps = 50;
const saturation = Math.floor(Math.random() * 40) + 60; // 60-100%
const lightness = Math.floor(Math.random() * 30) + 40; // 40-70%
const hue = Math.floor((usedColors.length * 360) / hueSteps) % 360;
const color = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
usedColors.push(color);
return color;
} else {
// 50
let attempts = 0;
let newColor;
do {
const hue = Math.floor(Math.random() * 360);
const saturation = Math.floor(Math.random() * 40) + 60; // 60-100%
const lightness = Math.floor(Math.random() * 30) + 40; // 40-70%
newColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
attempts++;
// 10
} while (existingColors.includes(newColor) && attempts < 10);
return newColor;
}
}
//
interface Project {
id: number
name: string
description: string
createTime: string
path: string
count: number
type: string
progress: number
}
interface Image {
id: number
name: string
path: string
dataId: number
annotation: Array<{
class_id: number
center_x: number
center_y: number
width: number
height: number
polygon_points: string
angle: string
}>
status: number
}
interface AnnotationType {
id: number
name: string
color: string
dataId: number
index: number
}
// Annotation
interface Annotation {
id: number;
classId: number;
className: string;
markId: number;
centerX: number;
centerY: number;
width: number;
height: number;
polygon_points: number;
anagle: number;
dataId: number;
annotationData: any; //
}
//
const selectedProjectId = ref<number | undefined>(undefined)
const currentProject = ref<Project | null>(null)
const currentImage = ref<Image | null>(null)
const projectList = ref<Project[]>([])
const imageList = ref<Image[]>([])
const annotationTypes = ref<AnnotationType[]>([])
const annotations = ref<Annotation[]>([])
//
const addTypeDialogVisible = ref(false)
const newTypeForm = reactive({
name: '',
color: '#409EFF',
dataId: 0
})
const typeFormRef = ref()
//
const activeLeftPanels = ref(['project'])
const activeRightPanels = ref(['images'])
// DOM
const annotatorContainer = ref<HTMLElement | null>(null)
const annotatorImage = ref<HTMLImageElement | null>(null)
// script setup
//
const formatTimestamp = (timestamp: string) => {
if (!timestamp) return ''
const date = new Date(timestamp)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
//
const getProjectList = async () => {
try {
// API
const response = await MarkApi.getProjectList()
projectList.value = response
console.log(projectList.value)
} catch (error) {
console.error('获取项目列表失败:', error)
}
}
//
const handleProjectChange = async (projectId: number) => {
//
currentProject.value = projectList.value.find(p => p.id === projectId) || null
if (currentProject.value) {
//
await Promise.all([
getImageList(projectId),
getAnnotationTypes(projectId)
])
}
}
//
const updateImageStatus = async (imageId: number, status: number) => {
try {
// API
await MarkApi.updateImageStatus({ id: imageId, status: status })
//
const image = imageList.value.find(img => img.id === imageId)
if (image) {
image.status = status
}
} catch (error) {
console.error('更新图片状态失败:', error)
}
}
//
const getImageList = async (projectId: number) => {
try {
const response = await MarkApi.getImageList({ dataId: projectId })
imageList.value = response
console.log(imageList.value);
} catch (error) {
console.error('获取图片列表失败:', error)
}
}
//
const getAnnotationTypes = async (projectId: number) => {
try {
const response = await MarkApi.getTypeList({ dataId: projectId })
annotationTypes.value = response
} catch (error) {
console.error('获取标注类型失败:', error)
}
}
// selectImage
const selectImage = async (image: Image) => {
currentImage.value = image;
updateImageStatus(image.id, 1);
// console.log(annotations.value);
//
if (annotations.value && annotations.value.length > 0) {
// annotations.value = image.annotation.map((anno: any) => ({
// id: anno.id || Date.now(),
// classId: anno.class_id,
// className: annotationTypes.value.find(type => type.id === anno.class_id)?.name || '',
// markId: anno.mark_id || 0,
// centerX: anno.center_x,
// centerY: anno.center_y,
// width: anno.width,
// height: anno.height,
// polygon_points: anno.polygon_points,
// anagle: anno.angle,
// dataId: anno.dataId || 0,
// annotationData: anno.annotationData || null
// }));
MarkApi.createMarkInfo(annotations.value)
annotations.value = [];
} else {
annotations.value = [];
}
// annotorious
initAnnotorious();
};
// W3C
const convertToW3CFormat = (annotation: any) => {
return {
"@context": "http://www.w3.org/ns/anno.jsonld",
"id": `#annotation-${annotation.class_id}`,
"type": "Annotation",
"body": [{
"type": "TextualBody",
"value": annotationTypes.value.find(t => t.id === annotation.class_id)?.name || '未知类型'
}],
"target": {
"source": currentImage.value?.path,
"selector": {
"type": "FragmentSelector",
"conformsTo": "http://www.w3.org/TR/media-frags/",
"value": `xywh=pixel:${annotation.center_x - annotation.width/2},${annotation.center_y - annotation.height/2},${annotation.width},${annotation.height}`
}
}
}
}
// handleAnnotationCreated
const handleAnnotationCreated = (annotation: any) => {
console.log('创建标注:', annotation);
//
const positionInfo = parseAnnotationPosition(annotation);
console.log(positionInfo);
if (positionInfo) {
// 使
let classId = null;
let className = '未分类';
let typeColor = null;
if (defaultTypeId.value) {
console.log(defaultTypeId.value);
const type = annotationTypes.value.find(t => t.id === defaultTypeId.value);
if (type) {
classId = defaultTypeId.value;
className = type.name;
typeColor = type.color;
}
}
//
const newAnnotation: Annotation = {
id: Date.now(),
classId: classId,
className: className,
markId: currentImage.value?.id,
centerX: positionInfo.center_x,
centerY: positionInfo.center_y,
width: positionInfo.width,
height: positionInfo.height,
polygon_points: 0,
anagle: 0,
dataId: currentImage.value?.id || 0,
annotationData: annotation
};
annotations.value.push(newAnnotation);
console.log(newAnnotation);
//
if (classId && typeColor && annoInstance) {
annotation.bodies[0] = typeColor;
annoInstance.setStyle((annotation, state) => {
if(annotation.bodies[0]){
const color = annotation.bodies[0];
console.log(color);
return {
fill: color,
fillOpacity: 0.25,
stroke: color,
strokeWidth: 2,
};
} else return null;
});
}
}
};
//
const handleAnnotationUpdated = (annotation: any, previous: any) => {
console.log('更新标注:', annotation);
//
const positionInfo = parseAnnotationPosition(annotation);
if (positionInfo) {
//
const index = annotations.value.findIndex((item: any) =>
item.annotationData && item.annotationData.id === annotation.id);
if (index !== -1) {
annotations.value[index] = {
...annotations.value[index],
center_x: positionInfo.center_x,
center_y: positionInfo.center_y,
width: positionInfo.width,
height: positionInfo.height,
annotationData: annotation
};
}
}
};
//
const handleAnnotationDeleted = (annotation: any) => {
console.log('删除标注:', annotation);
//
const index = annotations.value.findIndex((item: any) =>
item.annotationData && item.annotationData.id === annotation.id);
if (index !== -1) {
annotations.value.splice(index, 1);
}
};
//
const handleClickAnnotation = (annotation: any, shape: any, sel: any) => {
console.log('点击标注:', annotation);
//
};
//
const handleSelectionChanged = (selected: any[]) => {
console.log('选择变化:', selected);
//
};
// parseAnnotationPosition
const parseAnnotationPosition = (annotation: any) => {
try {
// Annotorious 3 - geometry
if (annotation.target?.selector?.geometry) {
const geometry = annotation.target.selector.geometry;
console.log('Rectangle geometry:', annotation.target.selector.type);
if (annotation.target.selector.type === 'RECTANGLE') {
// Rectangle : { type: 'Rectangle', coordinates: [x, y, width, height] }
// const [x, y, width, height] = geometry.coordinates;
return {
center_x: geometry.x,
center_y: geometry.y,
width: geometry.w,
height: geometry.h
};
} else if (geometry.type === 'Polygon') {
// Polygon
const coordinates = geometry.coordinates[0]; //
if (coordinates.length >= 4) {
//
let minX = Math.min(...coordinates.map((p: number[]) => p[0]));
let maxX = Math.max(...coordinates.map((p: number[]) => p[0]));
let minY = Math.min(...coordinates.map((p: number[]) => p[1]));
let maxY = Math.max(...coordinates.map((p: number[]) => p[1]));
return {
center_x: (minX + maxX) / 2,
center_y: (minY + maxY) / 2,
width: maxX - minX,
height: maxY - minY
};
}
}
}
// FragmentSelector
if (annotation.target?.selector?.type === 'FragmentSelector' && annotation.target?.selector?.value) {
const selector = annotation.target.selector;
// xywh "xywh=pixel:x,y,width,height"
const coords = selector.value.replace('xywh=pixel:', '').split(',');
if (coords.length === 4) {
const [x, y, width, height] = coords.map(Number);
return {
center_x: x + width / 2,
center_y: y + height / 2,
width: width,
height: height
};
}
}
//
if (annotation.target?.selector?.conformsTo) {
const selector = annotation.target.selector;
if (selector.value) {
const coords = selector.value.replace('xywh=pixel:', '').split(',');
if (coords.length === 4) {
const [x, y, width, height] = coords.map(Number);
return {
center_x: x + width / 2,
center_y: y + height / 2,
width: width,
height: height
};
}
}
}
console.warn('无法解析标注位置信息:', annotation);
return null;
} catch (error) {
console.error('解析标注位置信息失败:', error);
return null;
}
};
// setAnnotationType
const setAnnotationType = (annotationIndex: number, typeId: number) => {
const type = annotationTypes.value.find(t => t.id === typeId);
if (type && annoInstance) {
// console.log(type);
//
annotations.value[annotationIndex].className = type.name;
annotations.value[annotationIndex].color = type.color;
annotations.value[annotationIndex].typeId = typeId;
// console.log(annotations);
//
defaultTypeId.value = typeId;
// Annotorious
const annotationData = annotations.value[annotationIndex].annotationData;
annotationData.bodies[0] = type.color;
//
console.log(annotationData);
// DOM
if (annotationData) {
annoInstance.setStyle((annotationData, state) => {
if(annotationData.bodies[0]){
const color = annotationData.bodies[0];
console.log(color);
return {
fill: color,
fillOpacity:0.25 ,
stroke: color,
strokeWidth: 2,
};
}else return null;
})
}
console.log(annotations.value[annotationIndex]);
// annoInstance.update(annotationData);
}
};
//
const selectAnnotation = (annotation: any) => {
if (annoInstance && annotation.annotationData) {
//
//
// const event = new CustomEvent('annotationClicked', { detail: { annotation } });
// annoInstance.getElement().dispatchEvent(event);
}
};
//
const loadExistingAnnotations = () => {
console.log(currentImage.value);
if (annoInstance && currentImage.value && currentImage.value.id) {
MarkApi.getMarkInfoList(currentImage.value.id).then((res) => {
if (res) {
console.log(res);
res.forEach((item) => {
console.log(item);
annoInstance.addAnnotation(item.annotationData)
annotations.value.push(item)
});
}
});
}
};
//
const showAddTypeDialog = () => {
if (!selectedProjectId.value) {
alert('请先选择项目')
return
}
addTypeDialogVisible.value = true
//
newTypeForm.name = ''
newTypeForm.color = generateRandomColor()
newTypeForm.dataId = selectedProjectId.value
}
//
const addAnnotationType = async () => {
if (!typeFormRef.value) return
try {
await typeFormRef.value.validate()
const response = await MarkApi.createType({
name: newTypeForm.name,
color: newTypeForm.color,
dataId: newTypeForm.dataId
})
console.log(response);
addTypeDialogVisible.value = false
//
if (selectedProjectId.value) {
await getAnnotationTypes(selectedProjectId.value)
}
} catch (error) {
console.error('添加标注类型失败:', error)
alert('添加失败')
}
}
//
const deleteAnnotationType = async (typeId: number) => {
try {
if (confirm('确定要删除这个标注类型吗?')) {
await MarkApi.deleteType(typeId)
//
if (selectedProjectId.value) {
await getAnnotationTypes(selectedProjectId.value)
}
}
} catch (error) {
console.error('删除标注类型失败:', error)
alert('删除失败')
}
}
//
const editAnnotation = (annotation: any) => {
console.log('编辑标注:', annotation)
//
}
//
onMounted(() => {
getProjectList()
})
//
onUnmounted(() => {
if (annoInstance) {
annoInstance.destroy()
}
})
</script>
<style scoped lang="scss">
.mark-container {
display: flex;
height: 82vh;
padding: 10px;
box-sizing: border-box;
}
.left-container,
.right-container {
background-color: var(--el-bg-color-overlay);
border-radius: 4px;
overflow: hidden;
:deep(.el-collapse-item__header) {
font-weight: bold;
padding-left: 15px;
}
:deep(.el-collapse-item__content) {
padding: 15px;
}
}
.center-container {
flex: 1;
padding: 0 10px;
background-color: #f5f5f5;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
.annotator-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.annotator-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
}
}
.image-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
.image-item {
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 5px;
cursor: pointer;
transition: all 0.3s;
&:hover {
border-color: #409eff;
}
&.active {
border-color: #409eff;
background-color: #ecf5ff;
}
.image-wrapper {
position: relative;
width: 100%;
height: 80px;
overflow: hidden;
border-radius: 4px;
.thumbnail {
width: 100%;
height: 100%;
object-fit: contain;
}
.status-indicator {
position: absolute;
bottom: 5px;
right: 5px;
width: 20px;
height: 20px;
background-color: #67C23A;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
}
}
.image-name {
text-align: center;
margin-top: 5px;
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.color-circle {
width: 20px;
height: 20px;
border-radius: 50%;
margin: 0 auto;
border: 1px solid #eee;
}
.coordinates-info {
font-size: 12px;
div {
margin-bottom: 2px;
}
}
.type-header {
margin-bottom: 10px;
text-align: right;
}
.left-scrollbar {
height: calc(90vh - 430px);
}
.image-scrollbar {
height: calc(100vh - 300px);
}
</style>

@ -0,0 +1,138 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="项目id" prop="dataId">
<el-input v-model="formData.dataId" placeholder="请输入项目id" />
</el-form-item>
<el-form-item label="训练集比例" prop="train">
<el-input v-model="formData.train" placeholder="请输入训练集比例" />
</el-form-item>
<el-form-item label="验证图像比例" prop="val">
<el-input v-model="formData.val" placeholder="请输入验证图像比例" />
</el-form-item>
<el-form-item label="测试图像比例" prop="test">
<el-input v-model="formData.test" placeholder="请输入测试图像比例" />
</el-form-item>
<el-form-item label="轮次" prop="round">
<el-input v-model="formData.round" placeholder="请输入轮次" />
</el-form-item>
<el-form-item label="批次大小" prop="size">
<el-input v-model="formData.size" placeholder="请输入批次大小" />
</el-form-item>
<el-form-item label="图片大小(正方向,大于这个值进行缩放,小于这个值进行放大,不是正方形将图片周围涂黑)" prop="imageSize">
<el-input v-model="formData.imageSize" placeholder="请输入图片大小(正方向,大于这个值进行缩放,小于这个值进行放大,不是正方形将图片周围涂黑)" />
</el-form-item>
<el-form-item label="预选训练模型" prop="modelPath">
<el-input v-model="formData.modelPath" placeholder="请输入预选训练模型" />
</el-form-item>
<el-form-item label="训练图片路径" prop="path">
<el-input v-model="formData.path" placeholder="请输入训练图片路径" />
</el-form-item>
<el-form-item label="类型使用哪个gpu" prop="trainType">
<el-select v-model="formData.trainType" placeholder="请选择类型使用哪个gpu">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { TrainApi, TrainVO } from '@/api/annotation/train'
/** 训练 表单 */
defineOptions({ name: 'TrainForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
dataId: undefined,
train: undefined,
val: undefined,
test: undefined,
round: undefined,
size: undefined,
imageSize: undefined,
modelPath: undefined,
path: undefined,
trainType: undefined,
})
const formRules = reactive({
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await TrainApi.getTrain(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as TrainVO
if (formType.value === 'create') {
await TrainApi.createTrain(data)
message.success(t('common.createSuccess'))
} else {
await TrainApi.updateTrain(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
dataId: undefined,
train: undefined,
val: undefined,
test: undefined,
round: undefined,
size: undefined,
imageSize: undefined,
modelPath: undefined,
path: undefined,
trainType: undefined,
}
formRef.value?.resetFields()
}
</script>

@ -0,0 +1,287 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="项目id" prop="dataId">
<el-input
v-model="queryParams.dataId"
placeholder="请输入项目id"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="训练集比例" prop="train">
<el-input
v-model="queryParams.train"
placeholder="请输入训练集比例"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="验证图像比例" prop="val">
<el-input
v-model="queryParams.val"
placeholder="请输入验证图像比例"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="测试图像比例" prop="test">
<el-input
v-model="queryParams.test"
placeholder="请输入测试图像比例"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="轮次" prop="round">
<el-input
v-model="queryParams.round"
placeholder="请输入轮次"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="批次大小" prop="size">
<el-input
v-model="queryParams.size"
placeholder="请输入批次大小"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="图片大小(正方向,大于这个值进行缩放,小于这个值进行放大,不是正方形将图片周围涂黑)" prop="imageSize">
<el-input
v-model="queryParams.imageSize"
placeholder="请输入图片大小(正方向,大于这个值进行缩放,小于这个值进行放大,不是正方形将图片周围涂黑)"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="预选训练模型" prop="modelPath">
<el-input
v-model="queryParams.modelPath"
placeholder="请输入预选训练模型"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="训练图片路径" prop="path">
<el-input
v-model="queryParams.path"
placeholder="请输入训练图片路径"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="类型使用哪个gpu" prop="trainType">
<el-select
v-model="queryParams.trainType"
placeholder="请选择类型使用哪个gpu"
clearable
class="!w-240px"
>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['annotation:train:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['annotation:train:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="id" align="center" prop="id" />
<el-table-column label="项目id" align="center" prop="dataId" />
<el-table-column label="训练集比例" align="center" prop="train" />
<el-table-column label="验证图像比例" align="center" prop="val" />
<el-table-column label="测试图像比例" align="center" prop="test" />
<el-table-column label="轮次" align="center" prop="round" />
<el-table-column label="批次大小" align="center" prop="size" />
<el-table-column label="图片大小(正方向,大于这个值进行缩放,小于这个值进行放大,不是正方形将图片周围涂黑)" align="center" prop="imageSize" />
<el-table-column label="预选训练模型" align="center" prop="modelPath" />
<el-table-column label="训练图片路径" align="center" prop="path" />
<el-table-column label="类型使用哪个gpu" align="center" prop="trainType" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['annotation:train:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['annotation:train:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<TrainForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { TrainApi, TrainVO } from '@/api/annotation/train'
import TrainForm from './TrainForm.vue'
/** 训练 列表 */
defineOptions({ name: 'Train' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<TrainVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
dataId: undefined,
train: undefined,
val: undefined,
test: undefined,
round: undefined,
size: undefined,
imageSize: undefined,
modelPath: undefined,
path: undefined,
trainType: undefined,
createTime: [],
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await TrainApi.getTrainPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await TrainApi.deleteTrain(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await TrainApi.exportTrain(queryParams)
download.excel(data, '训练.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

@ -0,0 +1,84 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { TrainResultApi, TrainResultVO } from '@/api/annotation/trainresult'
/** 识别结果 表单 */
defineOptions({ name: 'TrainResultForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
})
const formRules = reactive({
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await TrainResultApi.getTrainResult(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as TrainResultVO
if (formType.value === 'create') {
await TrainResultApi.createTrainResult(data)
message.success(t('common.createSuccess'))
} else {
await TrainResultApi.updateTrainResult(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
}
formRef.value?.resetFields()
}
</script>

@ -0,0 +1,178 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="数据集" prop="dataId">
<el-input
v-model="queryParams.dataId"
placeholder="请输入数据集"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['annotation:train-result:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['annotation:train-result:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="id" align="center" prop="id" />
<el-table-column label="训练id" align="center" prop="trainId" />
<el-table-column label="输出路径" align="center" prop="path" />
<el-table-column label="识别结果rate" align="center" prop="rate" />
<el-table-column
label="修改时间"
align="center"
prop="updateTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="数据集" align="center" prop="dataId" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['annotation:train-result:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['annotation:train-result:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<TrainResultForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { TrainResultApi, TrainResultVO } from '@/api/annotation/trainresult'
import TrainResultForm from './TrainResultForm.vue'
/** 识别结果 列表 */
defineOptions({ name: 'TrainResult' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<TrainResultVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
dataId: undefined,
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await TrainResultApi.getTrainResultPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await TrainResultApi.deleteTrainResult(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await TrainResultApi.exportTrainResult(queryParams)
download.excel(data, '识别结果.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

@ -0,0 +1,106 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="项目id" prop="dataId">
<el-input v-model="formData.dataId" placeholder="请输入项目id" />
</el-form-item>
<el-form-item label="index" prop="index">
<el-input v-model="formData.index" placeholder="请输入index" />
</el-form-item>
<el-form-item label="颜色" prop="color">
<el-input v-model="formData.color" placeholder="请输入颜色" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { TypesApi, TypesVO } from '@/api/annotation/types'
/** 类别 表单 */
defineOptions({ name: 'TypesForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
name: undefined,
dataId: undefined,
index: undefined,
color: undefined,
})
const formRules = reactive({
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await TypesApi.getTypes(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as TypesVO
if (formType.value === 'create') {
await TypesApi.createTypes(data)
message.success(t('common.createSuccess'))
} else {
await TypesApi.updateTypes(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
dataId: undefined,
index: undefined,
color: undefined,
}
formRef.value?.resetFields()
}
</script>

@ -0,0 +1,220 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="项目id" prop="dataId">
<el-input
v-model="queryParams.dataId"
placeholder="请输入项目id"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="index" prop="index">
<el-input
v-model="queryParams.index"
placeholder="请输入index"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="颜色" prop="color">
<el-input
v-model="queryParams.color"
placeholder="请输入颜色"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['annotation:types:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['annotation:types:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="id" align="center" prop="id" />
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="项目id" align="center" prop="dataId" />
<el-table-column label="index" align="center" prop="index" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="颜色" align="center" prop="color" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['annotation:types:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['annotation:types:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<TypesForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { TypesApi, TypesVO } from '@/api/annotation/types'
import TypesForm from './TypesForm.vue'
/** 类别 列表 */
defineOptions({ name: 'Types' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<TypesVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
dataId: undefined,
index: undefined,
createTime: [],
color: undefined,
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await TypesApi.getTypesPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await TypesApi.deleteTypes(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await TypesApi.exportTypes(queryParams)
download.excel(data, '类别.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

@ -43,7 +43,7 @@
</div>
<Camera v-if="getCameraId((rowIndex - 1) * topNum + colIndex)!== 0"
:cameraId="getCameraId((rowIndex-1)*topNum +colIndex)" ref="camera" :ptzShow="false" />
:cameraId="getCameraId((rowIndex-1)*topNum +colIndex)" :ref="'camera'+((rowIndex-1)*topNum +colIndex)" :ptzShow="false" />
</el-col>
</el-row>
</el-col>
@ -57,8 +57,6 @@ import Camera from '@/components/camera/camera.vue'
import { ref } from 'vue';
import ptz from '@/components/camera/ptzNotFlow.vue'
import { CameraApi, CameraVO } from '@/api/logistics/camera'
import { el, ta } from 'element-plus/es/locale'
import { flatMap } from 'lodash-es'
/** 相机 列表 */
defineOptions({ name: 'Camera' })
@ -72,8 +70,17 @@ const cameraIndex = ref()
//
const clickCamera = ref(false)
const CameraMap = ref<Map<number, number | undefined>>()
const reVideo = async () => {
const reVideo = async (cameraId: number) => {
if (CameraMap.value) {
for (let i = 1; i <= topNum.value * topNum.value; i++) {
if (CameraMap.value.get(i) === cameraId) {
deleteCameraFun(i)
CameraMap.value?.set(i, cameraId)
}
}
}
}
/** 查询列表 */
const getTree = async () => {

@ -176,8 +176,8 @@
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
v-if="scope.row.videoPath1 && scope.row.videoPath1.trim() !== '' &&
scope.row.videoPath2 && scope.row.videoPath2.trim() !== '' &&
v-if="(scope.row.videoPath1 && scope.row.videoPath1.trim() !== '' ||
scope.row.videoPath2 && scope.row.videoPath2.trim() !== '') &&
scope.row.endTime"
link
type="primary"
@ -207,12 +207,12 @@
/>
<el-dialog v-model="dialogVisible" title="视频" width="500">
<video ref="videoPlayer1" width="100%" controls autoplay muted>
<video v-if="video1Src" ref="videoPlayer1" width="100%" controls autoplay muted>
<source :src="video1Src" type="video/mp4" />
您的浏览器不支持 video 标签
</video>
<video ref="videoPlayer2" width="100%" controls autoplay muted>
<video v-if="video2Src" ref="videoPlayer2" width="100%" controls autoplay muted>
<source :src="video2Src" type="video/mp4" />
您的浏览器不支持 video 标签
</video>

@ -0,0 +1,125 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="宽度" prop="width">
<el-input v-model="formData.width" placeholder="请输入宽度单位mm" />
</el-form-item>
<el-form-item label="长度" prop="length">
<el-input v-model="formData.length" placeholder="请输入长度单位mm" />
</el-form-item>
<el-form-item label="高度" prop="high">
<el-input v-model="formData.high" placeholder="请输入高度单位mm" />
</el-form-item>
<el-form-item label="整堆高度" prop="height">
<el-input v-model="formData.height" placeholder="请输入整堆高度单位mm" />
</el-form-item>
<el-form-item label="最小面积" prop="minArea">
<el-input v-model="formData.minArea" placeholder="请输入最小面积" />
</el-form-item>
<el-form-item label="截取高度" prop="tolerance">
<el-input v-model="formData.tolerance" placeholder="请输入截取高度" />
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-input v-model="formData.type" placeholder="请输入类型" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { SpecificationConfApi, SpecificationConfVO } from '@/api/specificationConf'
/** 品规配置 表单 */
defineOptions({ name: 'SpecificationConfForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
width: undefined,
length: undefined,
height: undefined,
minArea: undefined,
tolerance: undefined,
name: undefined,
type: undefined,
high: undefined,
})
const formRules = reactive({
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await SpecificationConfApi.getSpecificationConf(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as SpecificationConfVO
if (formType.value === 'create') {
await SpecificationConfApi.createSpecificationConf(data)
message.success(t('common.createSuccess'))
} else {
await SpecificationConfApi.updateSpecificationConf(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
width: undefined,
length: undefined,
height: undefined,
minArea: undefined,
tolerance: undefined,
name: undefined,
type: undefined,
high: undefined,
}
formRef.value?.resetFields()
}
</script>

@ -0,0 +1,193 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="类型" prop="name">
<el-input
v-model="queryParams.type"
placeholder="请输入类型"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['logistics:specification-conf:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['logistics:specification-conf:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="类型" align="center" prop="type" />
<el-table-column label="宽度" align="center" prop="width" />
<el-table-column label="长度" align="center" prop="length" />
<el-table-column label="整堆高度" align="center" prop="height" />
<el-table-column label="最小面积" align="center" prop="minArea" />
<el-table-column label="截取高度" align="center" prop="tolerance" />
<el-table-column label="高度" align="center" prop="high" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['logistics:specification-conf:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['logistics:specification-conf:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<SpecificationConfForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import download from '@/utils/download'
import { SpecificationConfApi, SpecificationConfVO } from '@/api/specificationConf'
import SpecificationConfForm from './SpecificationConfForm.vue'
/** 品规配置 列表 */
defineOptions({ name: 'SpecificationConf' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<SpecificationConfVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
width: undefined,
length: undefined,
height: undefined,
minArea: undefined,
tolerance: undefined,
name: undefined,
type: undefined,
high: undefined,
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await SpecificationConfApi.getSpecificationConfPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
console.log('openForm' + type+ id);
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await SpecificationConfApi.deleteSpecificationConf(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await SpecificationConfApi.exportSpecificationConf(queryParams)
download.excel(data, '品规配置.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

@ -0,0 +1,89 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="uuid" prop="uuid">
<el-input v-model="formData.uuid" placeholder="请输入uuid" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { WarnApi, WarnVO } from '@/api/logistics/warn'
/** 报警信息 表单 */
defineOptions({ name: 'WarnForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
uuid: undefined,
})
const formRules = reactive({
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await WarnApi.getWarn(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as WarnVO
if (formType.value === 'create') {
await WarnApi.createWarn(data)
message.success(t('common.createSuccess'))
} else {
await WarnApi.updateWarn(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
uuid: undefined,
}
formRef.value?.resetFields()
}
</script>

@ -0,0 +1,263 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="巷道标识" prop="streetId">
<el-input
v-model="queryParams.streetId"
placeholder="请输入巷道标识"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker
v-model="queryParams.startTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="位置" prop="location">
<el-input
v-model="queryParams.location"
placeholder="请输入位置"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="报警信号" prop="signal">
<el-input
v-model="queryParams.signal"
placeholder="请输入报警信号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="左右" prop="direction">
<el-input
v-model="queryParams.direction"
placeholder="请输入左右"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="内外" prop="side">
<el-input
v-model="queryParams.side"
placeholder="请输入内外"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="层" prop="row">
<el-input
v-model="queryParams.row"
placeholder="请输入层"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="列" prop="column">
<el-input
v-model="queryParams.column"
placeholder="请输入列"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="uuid" prop="uuid">
<el-input
v-model="queryParams.uuid"
placeholder="请输入uuid"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['logistics:warn:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['logistics:warn:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="巷道标识" align="center" prop="streetId" />
<el-table-column
label="开始时间"
align="center"
prop="startTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="位置" align="center" prop="location" />
<el-table-column label="报警信号" align="center" prop="signal" />
<el-table-column label="左右" align="center" prop="direction" />
<el-table-column label="内外" align="center" prop="side" />
<el-table-column label="层" align="center" prop="row" />
<el-table-column label="列" align="center" prop="column" />
<el-table-column label="uuid" align="center" prop="uuid" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['logistics:warn:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['logistics:warn:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<WarnForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { WarnApi, WarnVO } from '@/api/logistics/warn'
import WarnForm from './WarnForm.vue'
/** 报警信息 列表 */
defineOptions({ name: 'Warn' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<WarnVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
streetId: undefined,
startTime: [],
location: undefined,
signal: undefined,
direction: undefined,
side: undefined,
row: undefined,
column: undefined,
uuid: undefined,
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await WarnApi.getWarnPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await WarnApi.deleteWarn(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await WarnApi.exportWarn(queryParams)
download.excel(data, '报警信息.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
Loading…
Cancel
Save