This commit is contained in:
xhf 2025-02-13 14:16:37 +08:00
parent 66150c708d
commit 1b188bdf26
12 changed files with 636 additions and 345 deletions

View File

@ -4,7 +4,7 @@ NODE_ENV=development
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://192.168.0.66:48080'
VITE_BASE_URL='http://192.168.0.74:48080'
# VITE_BASE_URL='http://192.168.0.189:48080'
# 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务

View File

@ -1,27 +1,6 @@
import request from '@/config/axios'
//agv 下载
export const agvDownload = async (params) => {
return await request.download({ url: `/system/position-map/agvDownload`, params })
}
//点位地图列表
export const getPositionMapList = async (params) => {
return await request.get({ url: `/system/position-map/list`, params })
}
//删除车辆信息
export const deleteRobotInformation = async (id: number) => {
return await request.delete({ url: `/system/robot/information/delete?id=` + id })
}
//查询所有车辆
export const getAllRobot = async (data) => {
return await request.post({ url: `/system/robot/information/getAllRobot`, data })
}
//获得车辆信息 详情
export const getRobotInformation = async (params) => {
return await request.get({ url: `/system/robot/information/get`, params })
}
//分页查询设备列表 看板信息设备目前用的是这个
export const deviceInformationPage = async (params) => {
@ -32,25 +11,24 @@ export const robotInformationStatistics = async (data) => {
return await request.post({ url: `/system/robot/information/statistics`, data })
}
// 创建车辆信息
export const robotInformationCreate = async (data) => {
return await request.post({ url: `/system/robot/information/create`, data })
// 创建设备信息
export const deviceInformationCreate = async (data) => {
return await request.post({ url: `/system/device/information/create`, data })
}
// 编辑车辆信息
export const robotInformationUpdate = async (data) => {
return await request.put({ url: `/system/robot/information/update`, data })
// 编辑设备信息
export const deviceInformationUpdate = async (data) => {
return await request.put({ url: `/system/device/information/update`, data })
}
//分页查询车辆列表 看板信息车辆目前用的是这个
export const robotGetAllModel = async (params) => {
return await request.get({ url: `/system/robot/model/getAllModel`, params })
}
// 范围选择
export const robotPositionGetMap = async (params) => {
return await request.get({ url: `/system/position-map/getMap`, params })
}
// 完整范围选择
export const robotPositionGetMapAll = async (params) => {
return await request.get({ url: `/system/position-map/getAllMap`, params })
// 地图绑定设备
export const deviceInformationGet = async (params) => {
return await request.get({ url: `/system/device/information/get`, params })
}
//删除设备信息
export const deleteDeviceInformation = async (id: number) => {
return await request.delete({ url: `/system/device/information/delete?id=` + id })
}
// 获取设备列表
export const deviceNumber = async (params) => {
return await request.get({ url: `/system/device/information/deviceNumber`, params })
}

View File

@ -1,5 +1,6 @@
import UploadImg from './src/UploadImg.vue'
import UploadImgs from './src/UploadImgs.vue'
import UploadFile from './src/UploadFile.vue'
import UploadFileNew from './src/UploadFileNew.vue'
export { UploadImg, UploadImgs, UploadFile }
export { UploadImg, UploadImgs, UploadFile ,UploadFileNew}

View File

@ -0,0 +1,271 @@
<template>
<div class="upload-box">
<el-upload
:id="uuid"
:accept="fileType.join(',')"
:action="uploadUrl"
:before-upload="beforeUpload"
:class="['upload', drag ? 'no-border' : '']"
:disabled="disabled"
:drag="drag"
:http-request="httpRequest"
:multiple="false"
:on-error="uploadError"
:on-success="uploadSuccess"
:show-file-list="false"
>
<template v-if="modelValue">
<img :src="modelValue" class="upload-image" />
<div class="upload-handle" @click.stop>
<div v-if="!disabled" class="handle-icon" @click="editImg">
<Icon icon="ep:edit" />
<span v-if="showBtnText">{{ t('action.edit') }}</span>
</div>
<div class="handle-icon" @click="imagePreview(modelValue)">
<Icon icon="ep:zoom-in" />
<span v-if="showBtnText">{{ t('action.detail') }}</span>
</div>
<div v-if="showDelete && !disabled" class="handle-icon" @click="deleteImg">
<Icon icon="ep:delete" />
<span v-if="showBtnText">{{ t('action.del') }}</span>
</div>
</div>
</template>
<template v-else>
<div class="upload-empty">
<slot name="empty">
<Icon icon="ep:plus" />
<!-- <span>请上传图片</span> -->
</slot>
</div>
</template>
</el-upload>
<div class="el-upload__tip">
<slot name="tip"></slot>
</div>
</div>
</template>
<script lang="ts" setup>
import type { UploadProps } from 'element-plus'
import { generateUUID } from '@/utils'
import { propTypes } from '@/utils/propTypes'
import { createImageViewer } from '@/components/ImageViewer'
import { useUpload } from '@/components/UploadFile/src/useUpload'
defineOptions({ name: 'UploadImg' })
type FileTypes =
| 'image/apng'
| 'image/bmp'
| 'image/gif'
| 'image/jpeg'
| 'image/pjpeg'
| 'image/png'
| 'image/svg+xml'
| 'image/tiff'
| 'image/webp'
| 'image/x-icon'
//
const props = defineProps({
modelValue: propTypes.string.def(''),
drag: propTypes.bool.def(true), // ==> true
disabled: propTypes.bool.def(false), // ==> false
fileSize: propTypes.number.def(5), // ==> 5M
fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // ==> ["image/jpeg", "image/png", "image/gif"]
height: propTypes.string.def('150px'), // ==> 150px
width: propTypes.string.def('150px'), // ==> 150px
borderradius: propTypes.string.def('8px'), // ==> 8px
showDelete: propTypes.bool.def(true), //
showBtnText: propTypes.bool.def(true) //
})
const { t } = useI18n() //
const message = useMessage() //
// id
const uuid = ref('id-' + generateUUID())
//
const imagePreview = (imgUrl: string) => {
createImageViewer({
zIndex: 9999999,
urlList: [imgUrl]
})
}
const emit = defineEmits(['update:modelValue'])
const deleteImg = () => {
emit('update:modelValue', '')
}
const { uploadUrl, httpRequest } = useUpload()
const editImg = () => {
const dom = document.querySelector(`#${uuid.value} .el-upload__input`)
dom && dom.dispatchEvent(new MouseEvent('click'))
}
const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
const imgType = props.fileType
if (!imgType.includes(rawFile.type as FileTypes))
message.notifyWarning('上传图片不符合所需的格式!')
if (!imgSize) message.notifyWarning(`上传图片大小不能超过 ${props.fileSize}M`)
return imgType.includes(rawFile.type as FileTypes) && imgSize
}
//
const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
message.success('上传成功')
emit('update:modelValue', res.data)
}
//
const uploadError = () => {
message.notifyError('图片上传失败,请您重新上传!')
}
</script>
<style lang="scss" scoped>
.is-error {
.upload {
:deep(.el-upload),
:deep(.el-upload-dragger) {
border: 1px dashed var(--el-color-danger) !important;
&:hover {
border-color: var(--el-color-primary) !important;
}
}
}
}
:deep(.disabled) {
.el-upload,
.el-upload-dragger {
cursor: not-allowed !important;
background: var(--el-disabled-bg-color);
border: 1px dashed var(--el-border-color-darker) !important;
&:hover {
border: 1px dashed var(--el-border-color-darker) !important;
}
}
}
.upload-box {
.no-border {
:deep(.el-upload) {
border: none !important;
}
}
:deep(.upload) {
.el-upload {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: v-bind(width);
height: v-bind(height);
overflow: hidden;
border: 1px dashed var(--el-border-color-darker);
border-radius: v-bind(borderradius);
transition: var(--el-transition-duration-fast);
&:hover {
border-color: var(--el-color-primary);
.upload-handle {
opacity: 1;
}
}
.el-upload-dragger {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 0;
overflow: hidden;
background-color: transparent;
border: 1px dashed var(--el-border-color-darker);
border-radius: v-bind(borderradius);
&:hover {
border: 1px dashed var(--el-color-primary);
}
}
.el-upload-dragger.is-dragover {
background-color: var(--el-color-primary-light-9);
border: 2px dashed var(--el-color-primary) !important;
}
.upload-image {
width: 100%;
height: 100%;
object-fit: contain;
}
.upload-empty {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
line-height: 30px;
color: var(--el-color-info);
.el-icon {
font-size: 28px;
color: var(--el-text-color-secondary);
}
}
.upload-handle {
position: absolute;
top: 0;
right: 0;
display: flex;
width: 100%;
height: 100%;
cursor: pointer;
background: rgb(0 0 0 / 60%);
opacity: 0;
box-sizing: border-box;
transition: var(--el-transition-duration-fast);
align-items: center;
justify-content: center;
.handle-icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 6%;
color: aliceblue;
.el-icon {
margin-bottom: 40%;
font-size: 130%;
line-height: 130%;
}
span {
font-size: 85%;
line-height: 85%;
}
}
}
}
}
.el-upload__tip {
line-height: 18px;
text-align: center;
}
}
</style>

View File

@ -12,28 +12,33 @@
<div class="new-top-box-left-item" style="color: #00329f">
<div class="new-top-box-left-item-name">工作</div>
<div class="new-top-box-left-item-value">{{ carStatistics.inTask || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
<div class="new-top-box-left-item" style="color: #e07300">
<div class="grey-line"> </div>
<div class="new-top-box-left-item" style="color: #e07300;background: #EBF1FF;">
<div class="new-top-box-left-item-name">充电</div>
<div class="new-top-box-left-item-value">{{ carStatistics.charge || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
<div class="grey-line" > </div>
<div class="new-top-box-left-item" style="color: #c60606">
<div class="new-top-box-left-item-name">异常</div>
<div class="new-top-box-left-item-value">{{ carStatistics.fault || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
<div class="grey-line" > </div>
<div class="new-top-box-left-item" style="color: #f1cd0b">
<div class="new-top-box-left-item-name">锁定</div>
<div class="new-top-box-left-item-value">{{ carStatistics.doLock || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
<div class="grey-line" > </div>
<div class="new-top-box-left-item" style="color: #4dc606">
<div class="new-top-box-left-item-name">待命</div>
<div class="new-top-box-left-item-value">{{ carStatistics.standby || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
<div class="grey-line"> </div>
<div class="new-top-box-left-item" style="color: #7a7a7a">
<div class="new-top-box-left-item-name">离线</div>
<div class="new-top-box-left-item-value">{{ carStatistics.offline || 0 }}</div>
@ -106,7 +111,7 @@
<div class="item-inner-left">
<div class="item-inner-left-img-box">
<el-image style="width: 100%; height: 100%" :src="url" :fit="'fill'" />
<el-image style="width: 100%; height: 100%" :src="item.url" :fit="'fill'" />
</div>
<div class="item-inner-left-bottom">
<div class="item-inner-left-bottom-btn" @click="goMap(item)"> 地图定位 </div>
@ -478,8 +483,9 @@ onBeforeRouteLeave((to, from, next) => {
PingFang SC;
font-weight: 600;
font-size: 16px;
margin-left: 10px;
padding: 5px 0;
padding: 5px 10px;
cursor: pointer;
}
.new-top-box-left-item-name {
flex-shrink: 0;

View File

@ -1,49 +1,35 @@
<template>
<Dialog v-model="dialogVisible" :title="title" width="800" class="task-dialog">
<Dialog v-model="dialogVisible" :title="title" width="545" class="task-dialog">
<el-form :model="formData" label-width="auto" ref="formRef" :rules="formRules">
<el-form-item label="车辆类型" prop="robotModelId" required>
<el-select v-model="formData.robotModelId" placeholder="请选择车辆类型" required>
<el-form-item label="设备类型" prop="robotModelId" >
<el-select v-model="formData.deviceType" clearable placeholder="请选择设备类型">
<el-option
:label="item.robotModelNumber"
:value="item.id"
v-for="item in carModelList"
:key="item.id"
v-for="dict in getDictOptions(DICT_TYPE.DEVICE_TYPE)"
:key="dict.value"
:label="dict.label"
:value="Number(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item required label="车辆编号" prop="robotNo">
<el-input v-model="formData.robotNo" :disabled="false" placeholder="请输入车辆编号"/>
<el-form-item label="设备编号" prop="robotNo">
<el-input v-model="formData.deviceNo" :disabled="false" placeholder="请输入设备编号" />
</el-form-item>
<el-form-item required label="任务模式" v-if="formData.id">
<el-select v-model="formData.robotTaskModel" placeholder="请选择车辆类型" required>
<el-option
:label="'拒收任务'"
:value="0"
/>
<el-option
:label="'正常'"
:value="1"
/>
</el-select>
<el-form-item label="Mac地址" prop="macAddress">
<el-input v-model="formData.macAddress" :disabled="false" placeholder="请输入Mac地址" />
</el-form-item>
<el-form-item required label="自动充电电量" prop="autoCharge">
<el-input-number v-model="formData.autoCharge" :disabled="false" :controls="false" :precision="0" :step="1" type="number" :min="0" :max="99" placeholder="请输入自动充电电量">
<template #append>%</template>
</el-input-number>
<span style="margin-left: 20px;">%</span>
<el-form-item label="设备图标" prop="macAddress">
<UploadImg v-model="formData.mapImageUrl" :limit="1" />
</el-form-item>
<el-form-item required label="Mac地址" prop="macAddress">
<el-input v-model="formData.macAddress" :disabled="false" placeholder="请输入Mac地址"/>
<el-form-item label="设备图配置" prop="pictureConfig">
<el-radio-group v-model="formData.pictureConfig">
<el-radio :label="1">默认图片</el-radio>
<el-radio :label="2">上传图片</el-radio>
<el-radio :label="3">不显示图片</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item required label="选择范围" placeholder="请选择范围">
<el-cascader
:options="floorAreaList"
:props="props"
clearable
:collapse-tags="false"
v-model="floorAreaJsonData"
@change="floorAreaChange"
/>
<el-form-item label="设备图片" prop="mapImageUrl" v-if="formData.pictureConfig === 2">
<UploadImg v-model="formData.url" :limit="1" />
</el-form-item>
</el-form>
@ -57,77 +43,42 @@
const { t } = useI18n() //
const message = useMessage() //
import * as MapTaskAPi from '@/api/map/mapTask'
import * as CarApi from '@/api/car/index'
import * as DeviceApi from '@/api/device/index'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
const dialogVisible = ref(false) //
const formLoading = ref(false) // 12
const title = ref('新建') // form
const formData = ref({
robotModelId: undefined, //id
robotNo: undefined, //AGV
deviceType: undefined, //id
deviceNo: undefined, //AGV
macAddress: undefined, //mac
floorAreaJson: [],
autoCharge: undefined
mapImageUrl: undefined, //
pictureConfig: undefined, // 12 3
url: undefined //
})
const carModelList = ref([])
const floorAreaList = ref([])
const props = { multiple: true }
const floorAreaJsonData = ref([])
const getCarModelList = async () => {
const res = await CarApi.robotGetAllModel()
carModelList.value = res
}
const getFloorArea = async () => {
const res = await CarApi.robotPositionGetMapAll()
let data = []
let floor = []
data = res
for(let key in res){
let obj = {
value: key,
label: key + '层',
children: res[key].map(item => {return {label: item.area, value: item.id,...item,children: []}})
}
floor.push(obj)
}
console.log(floor)
floorAreaList.value = floor
}
const floorAreaChange = (value) => {
console.log(value)
formData.value.floorAreaJson = []
value.forEach(item => {
formData.value.floorAreaJson.push(item[1])
})
console.log(formData.value.floorAreaJson)
}
const formRules = reactive({
robotModelId: [{ required: true, message: '车辆类型不能为空', trigger: 'change' }],
robotNo: [{ required: true, message: 'AGV编号不能为空', trigger: 'change' }],
macAddress: [{ required: true, message: 'Mac地址不能为空', trigger: 'change' }],
autoCharge: [{ required: true, message: '自动充电电量不能为空', trigger: 'change' }]
deviceType: [{ required: true, message: '设备类型不能为空', trigger: 'blur' }],
deviceNo: [{ required: true, message: '设备编号不能为空', trigger: 'blur' }],
macAddress: [{ required: true, message: 'Mac地址不能为空', trigger: 'blur' }],
mapImageUrl: [{ required: true, message: '设备图标不能为空', trigger: 'blur' }],
pictureConfig: [{ required: true, message: '设备图配置不能为空', trigger: 'blur' }],
url: [{ required: true, message: '设备图片不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type, id) => {
floorAreaJsonData.value = []
getCarModelList()
getFloorArea()
dialogVisible.value = true
resetForm()
if (id) {
title.value = '编辑'
const data = await CarApi.getRobotInformation({id})
const data = await DeviceApi.deviceInformationGet({ id })
formData.value = data
if(data.positionMapList.length){
data.positionMapList.forEach(item => {
floorAreaJsonData.value.push([item.floor,item.id])
})
}
console.log(data)
} else {
title.value = '新建'
@ -144,23 +95,20 @@ const submitForm = async () => {
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
if(!formData.value.floorAreaJson.length){
message.warning('请选择范围')
return
}
//
formLoading.value = true
try {
if(formData.value.id){
await CarApi.robotInformationUpdate(formData.value)
if (formData.value.id) {
await DeviceApi.deviceInformationUpdate(formData.value)
message.success(t('common.updateSuccess'))
dialogVisible.value = false
emit('success')
} else {
await CarApi.robotInformationCreate(formData.value)
await DeviceApi.deviceInformationCreate(formData.value)
message.success(t('common.createSuccess'))
dialogVisible.value = false
emit('success')
emit('success')
}
} finally {
formLoading.value = false
@ -214,13 +162,14 @@ const takeRemoteMethod = async (query, item) => {
/** 重置表单 */
const resetForm = () => {
formData.value = {
robotModelId: undefined, //id
robotNo: undefined, //AGV
deviceType: undefined, //id
deviceNo: undefined, //AGV
macAddress: undefined, //mac
floorAreaJson: [],
autoCharge: undefined
mapImageUrl: undefined, //
pictureConfig: undefined, // 12 3
url: undefined //
}
floorAreaJsonData.value = []
formRef.value?.resetFields()
}
</script>

View File

@ -6,117 +6,30 @@
<div class="new-top-box-left-title"> 设备看板 </div>
</el-col>
<el-col :span="16">
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first1" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first2" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first3" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first4" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first5" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<el-tab-pane :label="'总数' + carStatistics.total || 0" name="first" />
<div style="width: 100%;height: 100%;padding-top: 5px;">
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane :label="item.label + ' ' + (item.number?item.number: 0)" :name="item.value" v-for="(item, index) in typeList" :key="index"/>
</el-tabs>
</div>
</el-col>
<el-col :span="6">
<!-- <div class="new-top-box-right">
<div class="new-top-box-right-input-box">
<input
type="text"
v-model="queryParams.robotNo"
placeholder="请输入关键字"
class="new-top-box-right-input"
placeholder-class="new-top-box-right-input-placeholder"
/>
<Icon
icon="ep:search"
size="20px"
color="#A4AFCA"
style="cursor: pointer"
@click="getCarList"
class="new-top-box-right-input-icon"
/>
</div>
<el-button>Default</el-button>
<div class="new-top-box-right-button" @click="openForm('create')"> 新增设备 </div>
</div> -->
<div style="display: flex">
<el-input
style="height: 40px"
v-model="queryParams.robotNo"
placeholder="Please Input"
:suffix-icon="Search"
/>
<el-button style="height: 40px">Default</el-button>
style="height: 40px;margin-right: 16px;"
v-model="queryParams.deviceNo"
placeholder="请输入关键词"
>
<template #append>
<el-button :icon="'search'" @click="getCarList"/>
</template>
</el-input>
<el-button style="height: 40px" @click="openForm('create')">新增设备</el-button>
</div>
</el-col>
</el-row>
</div>
<!-- <div class="new-top-box-left-statistics-box">
<div class="new-top-box-left-statistics-box-inner" v-if="carStatistics">
<div class="new-top-box-left-statistics"> 总数 {{ carStatistics.total || 0 }} </div>
<div class="grey-line"> </div>
<div class="new-top-box-left-item">
<div class="new-top-box-left-item-name">充电桩</div>
<div class="new-top-box-left-item-value">{{ carStatistics.inTask || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
<div class="new-top-box-left-item">
<div class="new-top-box-left-item-name">输送线</div>
<div class="new-top-box-left-item-value">{{ carStatistics.charge || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
<div class="new-top-box-left-item">
<div class="new-top-box-left-item-name">码垛机</div>
<div class="new-top-box-left-item-value">{{ carStatistics.fault || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
<div class="new-top-box-left-item">
<div class="new-top-box-left-item-name">自动门</div>
<div class="new-top-box-left-item-value">{{ carStatistics.fault || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
<div class="new-top-box-left-item">
<div class="new-top-box-left-item-name">提升机</div>
<div class="new-top-box-left-item-value">{{ carStatistics.fault || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
<div class="new-top-box-left-item">
<div class="new-top-box-left-item-name">信号灯</div>
<div class="new-top-box-left-item-value">{{ carStatistics.fault || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
<div class="new-top-box-left-item">
<div class="new-top-box-left-item-name">按钮盒</div>
<div class="new-top-box-left-item-value">{{ carStatistics.fault || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
<div class="new-top-box-left-item">
<div class="new-top-box-left-item-name">拆垛机</div>
<div class="new-top-box-left-item-value">{{ carStatistics.fault || 0 }}</div>
<div class="grey-line" style="margin-left: 8px"> </div>
</div>
</div>
</div> -->
</ContentWrap>
<div class="new-list-box-all">
<div class="new-list-box">
@ -124,7 +37,7 @@
<div class="item-top">
<div class="item-inner-left-name">
<div class="item-inner-left-name-inner">
{{ formatterDeviceType(item.deviceType) }} {{ item.deviceNo }}
{{ filterTypeFun(item.deviceType, typeList) }} {{ item.deviceNo }}
</div>
</div>
<div class="item-inner-right-top">
@ -136,9 +49,6 @@
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="openForm('update', item.id)">编辑</el-dropdown-item>
<el-dropdown-item @click="clockCar(item)">{{
item.status == 0 ? '解锁' : '锁定'
}}</el-dropdown-item>
<el-dropdown-item @click="deleteCar(item.id)">删除</el-dropdown-item>
</el-dropdown-menu>
</template>
@ -149,7 +59,10 @@
<div class="item-inner">
<div class="item-inner-left">
<div class="item-inner-left-img-box">
<el-image style="width: 100%; height: 100%" :src="url" :fit="'fill'" />
<el-image style="width: 100%; height: 100%" :src="item.url" :fit="'fill'" v-if="item.pictureConfig!=3"/>
<div class="item-inner-left-img-box-no" v-else>
<span >不显示图片</span>
</div>
</div>
<div class="item-inner-left-bottom">
<div class="item-inner-left-bottom-btn" @click="goMap(item)"> 地图定位 </div>
@ -157,15 +70,15 @@
</div>
</div>
<div class="item-inner-right-msg">
<!-- <div class="item-inner-right-msg-item m-b-10">
<div class="item-inner-right-msg-item-name">电量</div>
<div class="item-inner-right-msg-item-value" :style="{color:item.electricity>20?'#4DC606':'#C60606'}" v-if="item.electricity">{{ item.electricity || '' }} %</div>
</div> -->
<div class="item-inner-right-msg-item m-b-10">
<div class="item-inner-right-msg-item-name">编号</div>
<div class="item-inner-right-msg-item-value">{{ item.deviceNo || ''}}</div>
</div>
<div class="item-inner-right-msg-item m-b-10">
<div class="item-inner-right-msg-item-name">是否启用</div>
<div class="item-inner-right-msg-item-value">
<span v-if="item.robotStatus == 0" style="color: #c60606">禁用</span>
<span v-if="item.robotStatus == 1" style="color: #4dc606">启用</span>
<span v-if="item.deviceEnable == 0" style="color: #c60606">禁用</span>
<span v-if="item.deviceEnable == 1" style="color: #4dc606">启用</span>
</div>
</div>
<div class="item-inner-right-msg-item m-b-10">
@ -206,19 +119,36 @@ import createEditDialog from './createEditDialog.vue'
import 'swiper/css'
import { formatter } from 'element-plus'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
// DEVICE_TYPE
const router = useRouter() //
const createEditDialogRef = ref(null)
const list = ref([])
const queryParams = reactive({
pageNo: 1,
pageSize: 100,
robotNo: undefined
deviceNo: undefined,
deviceType: undefined
})
const spaceBetween = ref(20)
const navigation = ref({
nextEl: '.swiper-button-next-custome',
prevEl: '.swiper-button-prev-custome'
})
const activeName = ref('-1')
const handleClick = (tab, event) => {
console.log(tab, event)
queryParams.deviceType = tab.props.name !=-1?tab.props.name:undefined
getCarList()
}
const typeList = ref([])
const getTypeList = () => {
typeList.value = getDictOptions(DICT_TYPE.DEVICE_TYPE)&&getDictOptions(DICT_TYPE.DEVICE_TYPE).length?JSON.parse(JSON.stringify(getDictOptions(DICT_TYPE.DEVICE_TYPE))):[]
console.log(typeList.value)
typeList.value.unshift({ label: '全部', value: '-1' })
getRobotInformationStatistics()
}
// modules使
const modules = [Autoplay, Navigation, Scrollbar]
// Pagination
@ -233,6 +163,17 @@ const onSlideChange = (swiper) => {
// swiperswiperactiveIndex
console.log(swiper.activeIndex)
}
//type
const filterTypeFun = (type, list) => {
if (list.length) {
let obj = list.find(item => {
return item.value == type
})
return obj == undefined ? type : obj.label
} else {
return type
}
}
// (1:,2:线,3:,4:,5:,6:,7:,8:)
const formatterDeviceType = (deviceType) => {
switch (deviceType) {
@ -254,60 +195,18 @@ const formatterDeviceType = (deviceType) => {
return '拆垛机'
}
}
const topList = ref([
{
name: '总数',
count: 0,
id: -1
},
{
name: '充电桩',
count: 0,
id: 1
},
{
name: '输送线',
count: 0,
id: 2
},
{
name: '码垛机',
count: 0,
id: 3
},
{
name: '自动门',
count: 0,
id: 4
},
{
name: '提升机',
count: 0
},
{
name: '信号灯',
count: 0
},
{
name: '按钮盒',
count: 0
},
{
name: '拆垛机',
count: 0
}
])
const timerRef = ref(null)
//
const getCarList = async () => {
if (timerRef.value) {
clearInterval(timerRef.value)
timerRef.value = null
}
timerRef.value = setInterval(() => {
getCarList()
getRobotInformationStatistics()
}, 5000)
// if (timerRef.value) {
// clearInterval(timerRef.value)
// timerRef.value = null
// }
// timerRef.value = setInterval(() => {
// getCarList()
// getRobotInformationStatistics()
// }, 5000)
let res = await DeviceApi.deviceInformationPage(queryParams)
// console.log(res.list)
list.value = res.list
@ -323,27 +222,25 @@ const carStatistics = ref({
})
//
const getRobotInformationStatistics = async () => {
let res = await DeviceApi.robotInformationStatistics({})
console.log('车辆统计', res)
let res = await DeviceApi.deviceNumber({})
console.log('设备统计', res)
let total = 0
if (res) {
carStatistics.value = res
carStatistics.value.total =
Number(carStatistics.value.standby) +
Number(carStatistics.value.inTask) +
Number(carStatistics.value.doLock) +
Number(carStatistics.value.offline) +
Number(carStatistics.value.fault) +
Number(carStatistics.value.charge)
} else {
carStatistics.value = {
standby: 0,
inTask: 0,
doLock: 0,
offline: 0,
fault: 0,
charge: 0,
total: 0
if(typeList.value.length){
typeList.value.forEach(item => {
res.forEach(resItem => {
if(item.value == resItem.deviceType){
item.number = resItem.number
total += Number(resItem.number)
}
})
})
}
typeList.value[0].number = total
} else {
typeList.value.forEach(item => {
item.number = 0
})
}
}
//
@ -352,13 +249,13 @@ const openForm = (type, id) => {
}
//
const deleteCar = (id) => {
ElMessageBox.confirm('您确定要删除此车辆吗?', '提示', {
ElMessageBox.confirm('您确定要删除此设备吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
DeviceApi.deleteRobotInformation(id).then((res) => {
DeviceApi.deleteDeviceInformation(id).then((res) => {
getCarList()
message.success('删除成功')
})
@ -405,7 +302,9 @@ const goToMap = (item) => {
}
onMounted(() => {
getCarList()
getRobotInformationStatistics()
getTypeList()
// getRobotInformationStatistics()
})
onBeforeUnmount(() => {
if (timerRef.value) {
@ -439,7 +338,32 @@ onBeforeRouteLeave((to, from, next) => {
margin: 0px;
}
:deep(.is-active) {
background-color: #1677ff;
background-color: #EBF1FF;
color: #0D162A;
}
:deep(.el-tabs__item) {
padding-left: 8px !important;
padding-right: 8px !important;
height: auto !important;
padding-top: 5px !important;
padding-bottom: 5px !important;
}
:deep(.el-tabs__active-bar) {
height: 0;
}
:deep(.el-tabs__nav-next) {
line-height: 34px !important;
}
:deep(.el-tabs__nav-prev) {
line-height: 34px !important;
}
:deep(.el-input-group__append){
background: none !important;
}
:deep(.el-input__wrapper){
border: 1px solid var(--el-input-border-color,var(--el-border-color));
border-right: none;
box-shadow: none !important;
}
.swiper-container {
@ -771,4 +695,13 @@ input::-webkit-input-placeholder {
.m-b-10 {
margin-bottom: 10px;
}
.item-inner-left-img-box-no{
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
color: #a4afca;
}
</style>

View File

@ -37,7 +37,8 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-class-name="table-row-class"
:header-cell-style="{ backgroundColor: '#EBF1FF', color: '#0D162A' }">
<el-table-column label="序号" align="center" prop="id" />
<el-table-column label="告警等级1-4" align="center" prop="warnLevel" />
<el-table-column label="告警/异常ID" align="center" prop="warnCode" />
@ -186,3 +187,8 @@ onMounted(() => {
getList()
})
</script>
<style scoped>
::v-deep .table-row-class {
background-color: #f9f9f9e5;
}
</style>

View File

@ -39,10 +39,12 @@
<script setup>
import { ref, defineComponent, reactive, nextTick, onMounted } from 'vue'
import * as MapApi from '@/api/map/map'
import WebSocketClient from '../webSocket.js';
const imgUrl = ref('')
const socketClient = ref(null)
const list = ref([])
//
const nowObject = ref(null)
//
const getList = async () => {
let data = await MapApi.getPositionMapGetMap()
let mapList = []
@ -53,13 +55,38 @@ const getList = async () => {
})
}
list.value = mapList
console.log(list.value,data)
//
if (data[1][1]) {
getMapData(data[1][1])
if (data[1][0]) {
getMapData(data[1][0])
}
}
const getPositionMapList = async () => {
let data = await MapApi.getPositionMap()
console.log(data)
}
const replaceHttpWithWs = (str) => {
return str.replace(/^http/, 'ws');
}
const linkWebSocket = (url) => {
socketClient.value = new WebSocketClient(url);
if(socketClient.value){
//
socketClient.value.onMessage((message) => {
console.log('收到消息:', message);
});
}
}
const sendMessage = () => {
socketClient.value.send('Hello, WebSocket!');
};
const disconnect = () => {
socketClient.value.disconnect();
};
//
const getMapData = async (item) => {
let data = await MapApi.getPositionMapdDwnloadPngBase64({
@ -69,10 +96,15 @@ const getMapData = async (item) => {
let base64Url = 'data:image/png;base64,'
imgUrl.value = data
nowObject.value = JSON.parse(JSON.stringify(item))
let websoketUrl = `${replaceHttpWithWs(import.meta.env.VITE_BASE_URL)}/infra/ws?type=map&floor=${nowObject.value.floor}&area=${nowObject.value.area}`
console.log(websoketUrl)
linkWebSocket(websoketUrl)
}
onMounted(() => {
getList()
})
</script>

View File

@ -22,6 +22,7 @@ import { ref, defineComponent, reactive, nextTick, onMounted } from 'vue'
import * as MapApi from '@/api/map/map'
import download from '@/utils/download'
defineOptions({ name: 'MapPageRealTimeMap' })
const indexPageRef = ref(null)

View File

@ -0,0 +1,115 @@
// websocket.js
class WebSocketClient {
constructor(url) {
this.currentUrl = url;
this.socket = null;
this.heartbeatInterval = null;
this.messageCallback = null;
this.reconnectTimer = null;
this.reconnectAttempts = 0;
this.MAX_RECONNECT_ATTEMPTS = 5;
this.RECONNECT_DELAY = 5000; // 5 秒
this.HEARTBEAT_INTERVAL = 30000; // 30 秒
this.URL_CHECK_INTERVAL = 5000; // 每 5 秒检查一次 URL
this.init();
this.startUrlCheck();
}
init() {
try {
console.log('尝试创建 WebSocket 连接:', this.currentUrl);
this.socket = new WebSocket(this.currentUrl);
this.socket.onopen = () => {
console.log('WebSocket 连接已建立:', this.currentUrl);
this.startHeartbeat();
this.reconnectAttempts = 0;
};
this.socket.onmessage = (event) => {
if (this.messageCallback) {
this.messageCallback(event.data);
}
};
this.socket.onclose = () => {
console.log('WebSocket 连接已关闭:', this.currentUrl);
this.stopHeartbeat();
this.reconnect();
};
this.socket.onerror = (error) => {
console.error('WebSocket 发生错误:', error);
this.socket.close();
};
} catch (error) {
console.error('创建 WebSocket 连接时出错:', error);
}
}
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send('ping');
}
}, this.HEARTBEAT_INTERVAL);
}
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
onMessage(callback) {
this.messageCallback = callback;
}
send(message) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(message);
} else {
console.error('WebSocket 连接未打开,无法发送消息');
}
}
disconnect() {
if (this.socket) {
this.stopHeartbeat();
this.socket.close();
this.socket = null;
}
}
reconnect() {
if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
this.reconnectTimer = setTimeout(() => {
console.log('尝试重新连接...', this.currentUrl);
this.reconnectAttempts++;
this.init();
}, this.RECONNECT_DELAY);
} else {
console.error('达到最大重连次数,停止重连');
}
}
startUrlCheck() {
setInterval(() => {
const newUrl = this.getUpdatedUrl();
if (newUrl && newUrl!== this.currentUrl) {
this.disconnect();
this.currentUrl = newUrl;
this.init();
}
}, this.URL_CHECK_INTERVAL);
}
// 这个方法需要根据实际情况重写,用于获取最新的 URL
getUpdatedUrl() {
// 这里只是示例,返回 null 表示没有更新的 URL
return null;
}
}
export default WebSocketClient;

View File

@ -142,8 +142,7 @@
:step="1"
type="number"
:min="0"
:max="99999"
placeholder="充电阈值"
placeholder="充电周期"
>
<template #append></template>
</el-input-number>
@ -172,7 +171,7 @@
:step="1"
type="number"
:min="0"
:max="99"
placeholder="请输入等待时间"
>
<template #append></template>