Files
admin-govern/src/views/govern/manage/factory.vue
2026-06-18 16:35:16 +08:00

808 lines
31 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div v-loading="loading">
<TableHeader>
<template v-slot:select>
<el-form-item label="关键字筛选">
<el-input maxlength="32" show-word-limit v-model.trim="tableStore.table.params.searchValue"
style="width: 200px" clearable placeholder="请输入设备名称/网络设备ID" />
</el-form-item>
<!-- <el-form-item label="流程阶段">
<el-select v-model.trim="tableStore.table.params.process" clearable placeholder="请选择状态">
<el-option label="功能调试" :value="2"></el-option>
<el-option label="出厂调试" :value="3"></el-option>
<el-option label="正式投运" :value="4"></el-option>
</el-select>
</el-form-item> -->
<el-form-item label="物联状态">
<el-select v-model.trim="tableStore.table.params.connectStatus" clearable placeholder="请选择物联状态">
<el-option label="已连接" :value="1"></el-option>
<el-option label="未连接" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备状态">
<el-select v-model.trim="tableStore.table.params.runStatus" clearable placeholder="请选择状态">
<el-option label="在线" :value="2"></el-option>
<el-option label="离线" :value="1"></el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="设备类型">
<el-select
v-model.trim="tableStore.table.params.devType"
clearable
@change="devTypeChange"
placeholder="请选择设备类型"
>
<el-option
v-for="item in devTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="设备型号">
<el-select
v-model.trim="tableStore.table.params.devModel"
filterable
clearable
placeholder="请选择设备型号"
>
<el-option
v-for="item in devModelOptionsFilter"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="通讯协议">
<el-select
v-model.trim="tableStore.table.params.devAccessMethod"
clearable
placeholder="请选择通讯协议"
>
<el-option label="MQTT" value="MQTT"></el-option>
<el-option label="CLD" value="CLD"></el-option>
</el-select>
</el-form-item>-->
<!-- <el-form-item label="状态">
<el-select v-model.trim="tableStore.table.params.status" clearable placeholder="请选择状态">
<el-option label="未注册" :value="1"></el-option>
<el-option label="注册" :value="2"></el-option>
<el-option label="接入" :value="3"></el-option>
</el-select>
</el-form-item> -->
</template>
<template v-slot:operation>
<el-button type="primary" @click="downLoadFile1" class="ml10" icon="el-icon-Download">
模版下载
</el-button>
<el-upload style="display: inline-block" action="" accept=".xlsx" class="upload-demo"
:show-file-list="false" :auto-upload="false" :on-change="bulkImport">
<el-button type="primary" class="ml10" icon="el-icon-Tickets">批量导入</el-button>
</el-upload>
<el-button type="primary" class="ml10" @click="add" icon="el-icon-Plus">新增设备</el-button>
<el-button type="primary" class="ml10" icon="el-icon-Download" @click="downLoadQrCode"
:disabled="!showQrCode">
批量下载二维码
</el-button>
</template>
</TableHeader>
<Table ref="tableRef" :checkbox-config="checkboxConfig" :key="tableKey" @sort-change="handleSortChange"></Table>
<FactoryForm ref="factoryFormRef" :engineering-list="engineeringList" :dev-type-options="devTypeOptions"
:dev-type-options2="devTypeOptions2" :dev-model-options="devModelOptions"
:dev-model-options2="devModelOptions2" @success="onFormSuccess" />
<div class="qrcode-label">
<div class="qrcode-label-title">{{ deivce.mac }}</div>
<img class="qrcode-label-img" alt="二维码加载失败" :src="deivce.qrPath" />
</div>
<div v-if="qrcodeFlag">
<div class="qrcode-label" :class="`qrcode${i}`" v-for="(item, i) in tableStore.table.selection">
<div class="qrcode-label-title">{{ item.mac }}</div>
<img class="qrcode-label-img" alt="二维码加载失败" :src="fullUrl(item.qrPath)" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onActivated, provide, computed, reactive } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import FactoryForm from './components/factoryForm.vue'
import { queryByCode, queryByid, queryCsDictTree } from '@/api/system-boot/dictTree'
import { ElMessage, ElMessageBox } from 'element-plus'
import { activateUser, deluser, passwordConfirm } from '@/api/user-boot/user'
import { downLoadFile } from '@/api/cs-system-boot/manage'
import {
deleteEquipmentDelivery,
editEquipmentDelivery,
batchImportDevice,
getExcelTemplate,
engineeringProject,
onlineRegister,
resetFactory,
portableDeviceRegister,
portableDeviceAccess,
accessByUpdateMac
} from '@/api/cs-system-boot/device'
import html2canvas from 'html2canvas'
import { fullUrl } from '@/utils/common'
import JSZip from 'jszip'
import { saveAs } from 'file-saver'
defineOptions({
name: 'govern/manage/factory'
})
const showQrCode = ref(false)
const devTypeOptions: any = ref([])
const devTypeOptions2: any = ref([])
const devModelOptions2: any = ref([])
const engineeringList: any = ref([])
const deivce: any = ref({})
const factoryFormRef = ref<InstanceType<typeof FactoryForm>>()
const qrcodeFlag = ref(false)
const tableKey = ref(0)
const loading = ref<boolean>(false)
const devModelOptions: any = ref([])
const queryTheDictionary = () => {
queryByCode('DEV_CLD')
.then(res => {
devTypeOptions2.value = res.data
queryCsDictTree(res.data.id).then(res => {
devModelOptions2.value = res.data.map((item: any) => {
return {
value: item.id,
label: item.name,
...item
}
})
})
queryByCode('Device_Type').then(async res => {
const id = res.data.id
await queryCsDictTree(id).then(res => {
devTypeOptions.value = res.data.map((item: any) => {
return {
value: item.id,
label: item.name,
...item
}
})
// let index = devTypeOptions.value.findIndex((item: any) => {
// return item.name == '网关'
// })
// devTypeOptions.value.splice(index, 1)
})
await queryByid(id).then(res => {
res.data.map((item: any, index: any) => {
if (item.pid == id) {
res.data.splice(index, 1)
}
})
devModelOptions.value = res.data.map((item: any, index: any) => {
return {
value: item.id,
label: item.name,
...item
}
})
})
await tableStore.index()
})
})
.catch(error => {
console.error('查询过程中出现错误:', error)
})
}
const devModelOptionsFilter = computed(() => {
return devModelOptions.value.filter((item: any) => {
if (tableStore.table.params.devType) {
return item.pid == tableStore.table.params.devType
} else {
return true
}
})
})
const tableStore = new TableStore({
url: '/cs-device-boot/EquipmentDelivery/list',
method: 'POST',
isWebPaging: true,
publicHeight: 60,
column: [
{
width: '60',
type: 'checkbox'
},
{
title: '序号',
width: 80,
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ title: '设备名称', field: 'name', minWidth: 160 },
{ title: '网络设备ID', field: 'ndid', minWidth: 130 },
{
title: '设备类型',
field: 'devType',
formatter: row => {
if (row.row.devAccessMethod === 'CLD') {
// 遍历devTypeOptions2查找匹配
return devTypeOptions2.value.name
}
// 如果是MQTT协议使用devTypeOptions查找
else if (row.row.devAccessMethod === 'MQTT') {
const item = devTypeOptions.value.find((item: any) => item.value == row.cellValue)
return item ? item.label : '/'
}
},
minWidth: 110
},
{
title: '设备型号',
field: 'devModel',
formatter: row => {
const options = row.row.devAccessMethod === 'MQTT' ? devModelOptions.value : devModelOptions2.value
const item = options.find((item: any) => item.value == row.cellValue)
return item ? item.label : '/'
},
minWidth: 120
},
{
title: '通讯协议',
field: 'devAccessMethod',
formatter: row => {
return row.cellValue === 'MQTT' ? 'MQTT' : row.cellValue === 'CLD' ? 'CLD' : row.cellValue
},
minWidth: 100
},
{ title: '录入时间', field: 'createTime', sortable: true, minWidth: 150 },
{
title: '使用状态',
render: 'switch',
width: 100,
field: 'usageStatus',
activeText: '启用',
inactiveText: '停用',
inactiveValue: '0',
activeValue: '1',
onChangeField: (row: any, value: any) => {
// console.log("🚀 ~ row", row)
ElMessageBox.prompt('二次校验密码确认', '', {
confirmButtonText: '确认',
cancelButtonText: '取消',
customClass: 'customInput',
inputType: 'text',
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
if (instance.inputValue == null) {
return ElMessage.warning('请输入密码')
} else if (instance.inputValue?.length > 32) {
return ElMessage.warning(
'密码长度不能超过32位,当前密码长度为' + instance.inputValue.length
)
} else {
done()
}
} else {
done()
}
}
}).then(({ value }) => {
passwordConfirm(value).then(res => {
editEquipmentDelivery({
...row,
status: row.status == 5 ? 1 : row.status == 6 ? 2 : row.status,
usageStatus: row.usageStatus == 1 ? 0 : 1
}).then(res => {
ElMessage.success(row.usageStatus == 1 ? '设备停用成功!' : '设备启用成功!')
tableStore.index()
})
})
})
}
},
// { title: 'MQTT状态', field: 'connectStatus', width: 100, },
{
title: '物联状态',
field: 'connectStatus',
width: 100,
render: 'tag',
custom: {
未连接: 'danger',
已连接: 'success'
},
replaceValue: {
未连接: '未连接',
已连接: '已连接'
},
minWidth: 80
},
{
title: '设备状态',
field: 'runStatus',
width: 100,
render: 'tag',
custom: {
1: 'danger',
2: 'success'
},
replaceValue: {
1: '离线',
2: '在线'
},
minWidth: 80
},
// {
// title: '流程阶段',
// field: 'process',
// width: 100,
// render: 'tag',
// custom: {
// 2: 'warning',
// 3: 'warning',
// 4: 'success'
// },
// replaceValue: {
// 2: '功能调试',
// 3: '出厂调试',
// 4: '正式投运'
// },
// minWidth: 80
// },
{
title: '操作',
fixed: 'right',
align: 'center',
width: 180,
render: 'buttons',
buttons: [
// 在线设备注册
{
title: '接入',
type: 'primary',
icon: 'el-icon-Grid',
render: 'basicButton',
loading: 'loading1',
disabled: row => {
return !(
(row.devType == '123202e523be2b8defc8d0c2f118f232' || row.devType != '8b45cf6b7f5266e777d07c166ad5fa77') &&
row.associatedProject != null &&
(row.status == 5 || row.isAccess == 0)
)
},
click: row => {
// 便携式设备手动接入
ElMessageBox.confirm('确定接入该设备吗?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
row.loading1 = true
if (row.status == 5) {
// 在线设备注册
if (row.devType == '123202e523be2b8defc8d0c2f118f232') {
onlineRegister({
projectId: row.associatedProject,
nDid: row.ndid
})
.then(res => {
ElMessage.success(res.message)
row.loading1 = false
tableStore.index()
})
.catch(() => {
row.loading1 = false
})
} else {
// //便携式设备手动接入
portableDeviceRegister({
nDid: row.ndid
})
.then(res => {
row.loading1 = false
ElMessage.success(res.message)
tableStore.index()
})
.catch(e => {
row.loading1 = false
})
}
} else {
accessByUpdateMac({
nDid: row.ndid
})
.then(res => {
row.loading1 = false
ElMessage.success(res.message)
tableStore.index()
})
.catch(e => {
row.loading1 = false
})
}
})
}
},
//便携式设备手动接入
// {
// title: '接入',
// type: 'primary',
// icon: 'el-icon-Grid',
// render: 'basicButton',
// loading: 'loading1',
// disabled: row => {
// return (
// (row.devType != '8b45cf6b7f5266e777d07c166ad5fa77' &&
// row.devModel != 'a0d4da4b5c17b2172362a3f5a27bf217') ||
// row.status != '6'
// )
// },
// click: row => {
// // 便携式设备手动接入
// ElMessageBox.confirm('确定接入该设备吗?', '提示', {
// confirmButtonText: '确认',
// cancelButtonText: '取消',
// type: 'warning'
// })
// .then(() => {
// row.loading1 = true
// portableDeviceAccess({
// nDid: row.ndid
// }).then(res => {
// ElMessage.success(res.message)
// row.loading1 = false
// tableStore.index()
// })
// })
// .catch(e => {
// row.loading1 = false
// })
// }
// },
{
title: '接入',
type: 'primary',
icon: 'el-icon-Grid',
render: 'basicButton',
loading: 'loading1',
disabled: row => {
return (
(row.devType != '8b45cf6b7f5266e777d07c166ad5fa77' &&
row.devModel != 'a0d4da4b5c17b2172362a3f5a27bf217') ||
row.status != '5'
)
},
click: row => {
// 便携式设备接入
// portableDeviceAccess
ElMessageBox.confirm('确定接入该设备吗?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
row.loading1 = true
portableDeviceRegister({
nDid: row.ndid
})
.then(res => {
row.loading1 = false
ElMessage.success(res.message)
tableStore.index()
})
.catch(e => {
row.loading1 = false
})
})
}
},
{
title: '二维码',
type: 'primary',
icon: 'el-icon-Grid',
render: 'basicButton',
disabled: row => {
return !(row.devType == '2d4e186e2462590dedc765c5b6700a32')
},
click: row => {
deivce.value = row
deivce.value.qrPath = fullUrl(deivce.value.qrPath)
setTimeout(() => {
html2canvas(document.querySelector('.qrcode-label'), {
useCORS: true
}).then(canvas => {
let url = canvas.toDataURL('image/png')
// 下载图片
let a = document.createElement('a')
let event = new MouseEvent('click')
a.href = url
a.download = row.mac + '.png'
a.dispatchEvent(event)
})
}, 0)
}
},
{
name: 'edit',
title: '编辑',
type: 'primary',
icon: 'el-icon-EditPen',
render: 'basicButton',
click: async row => {
factoryFormRef.value?.openEdit(row)
}
},
{
name: 'edit',
title: '重置',
type: 'primary',
icon: 'el-icon-EditPen',
render: 'basicButton',
click: async row => {
ElMessageBox.confirm('确定重置该设备吗?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
resetFactory({
nDid: row.ndid
}).then(res => {
ElMessage.success('重置成功!')
tableStore.onTableAction('search', {})
})
})
}
},
{
name: 'del',
title: '删除',
type: 'danger',
icon: 'el-icon-Delete',
render: 'basicButton',
click: row => {
ElMessageBox.confirm('确定删除该设备吗?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
deleteEquipmentDelivery(row.id).then(res => {
ElMessage.success('删除成功!')
tableStore.onTableAction('search', {})
})
})
.catch(e => { })
}
}
]
}
],
beforeSearchFun: () => {
showQrCode.value = false
tableStore.table.column[0].type == 'checkbox' ? tableStore.table.column.splice(0, 1) : ''
},
loadCallback: () => {
tableStore.table.data.forEach(item => {
if (item.devType == '2d4e186e2462590dedc765c5b6700a32') {
showQrCode.value = true
tableStore.table.column[0].type != 'checkbox'
? tableStore.table.column.unshift({ type: 'checkbox', width: '60' })
: ''
}
})
tableKey.value += 1
}
})
tableStore.table.params.orderBy = 'desc'
tableStore.table.params.devType = ''
tableStore.table.params.devModel = ''
// tableStore.table.params.process = 2
tableStore.table.params.devAccessMethod = ''
tableStore.table.params.status = ''
tableStore.table.params.sortBy = ''
tableStore.table.params.orderBy = ''
tableStore.table.params.searchValue = ''
tableStore.table.params.connectStatus = ''
tableStore.table.params.runStatus = ''
// 设备类型
const devTypeChange = (e: any) => {
if (!e) {
return
}
tableStore.table.params.devModel = ''
}
// 下载模版
const downLoadFile1 = () => {
getExcelTemplate().then(res => {
let blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a') // 创建a标签
link.href = url
link.download = '设备导入模版.xlsx' // 设置下载的文件名
document.body.appendChild(link)
link.click() //执行下载
document.body.removeChild(link)
ElMessage.success('下载成功')
})
}
// 禁止点击
const checkboxConfig = reactive({
checkMethod: ({ row }) => {
return row.devType == '2d4e186e2462590dedc765c5b6700a32'
}
})
// 导入模版
const bulkImport = (e: any) => {
batchImportDevice(e.raw).then((res: any) => {
if (res.type === 'application/json') {
const reader = new FileReader()
reader.readAsText(res)
reader.onload = (e: any) => {
let data = JSON.parse(e.target.result)
if (data.code === 'A0000') {
ElMessage.success(data.message)
tableStore.onTableAction('search', {})
} else {
ElMessage.error(data.message)
}
}
} else {
ElMessage.error('导入失败!')
let url = window.URL.createObjectURL(res)
let link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', '导入失败.xlsx')
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
})
}
// 批量下载
const downLoadQrCode = async () => {
if (tableStore.table.selection.length == 0) {
ElMessage.warning('请选择需要下载的设备')
return
}
ElMessage.info('下载中,请稍后...')
qrcodeFlag.value = true
const zip = new JSZip()
// 创建一个数组来存储所有的异步操作
setTimeout(() => {
const promises = tableStore.table.selection.map(async (item: any, i: number) => {
// 等待 html2canvas 完成
const canvas = await html2canvas(document.querySelector(`.qrcode${i}`), {
useCORS: true
})
// 将 canvas 转换为 Base64
const url = canvas.toDataURL('image/png')
// 将文件添加到 ZIP 中
zip.file(item.mac + '.png', url.split(',')[1], { base64: true })
})
// 使用 Promise.all 等待所有异步操作完成
Promise.all(promises)
.then(() => {
// 所有文件都已添加到 ZIP 中,现在生成 ZIP 文件
zip.generateAsync({ type: 'blob' }).then(content => {
saveAs(content, `二维码.zip`) // 保存 ZIP 文件
})
})
.catch(error => {
console.error('生成 ZIP 文件时出错:', error)
})
qrcodeFlag.value = false
}, 500)
}
// 排序
const handleSortChange = ({ column, order }: { column: TableColumn; order: 'asc' | 'desc' | null }) => {
// console.log('排序列:', column?.property);
// console.log('排序顺序:', order);
// tableStore.onTableAction('sortable', { column, order })
tableStore.table.params.sortBy = column?.property
tableStore.table.params.orderBy = order
tableStore.table.params.pageNum = 1
tableStore.index()
// // 在这里可以根据 column 和 order 进行相应的数据排序操作
// if (order === 'asc') {
// } else if (order === 'desc') {
// }
}
// 示例用法
// 新增
const add = () => {
factoryFormRef.value?.openAdd()
}
const onFormSuccess = () => {
tableStore.onTableAction('search', {})
}
const getEngineeringList = () => {
return engineeringProject().then(res => {
engineeringList.value = res.data.filter(item => {
item.projectName = item.engineeringName
item.projectId = item.engineeringId
item.disabled = item.projectInfoList ? false : true
return item
})
})
}
// 页面被 keep-alive 缓存后,从工程页返回时刷新下拉列表
onActivated(() => {
if (sessionStorage.getItem('factoryNeedRefreshEngineeringList')) {
sessionStorage.removeItem('factoryNeedRefreshEngineeringList')
getEngineeringList()
}
})
provide('tableStore', tableStore)
onMounted(() => {
queryTheDictionary()
getEngineeringList()
setTimeout(() => { }, 100)
})
const addMenu = () => { }
</script>
<style lang="scss" scoped>
.qrcode-label {
position: absolute;
top: 0;
right: 0;
z-index: -99;
display: flex;
align-items: center;
justify-content: center;
width: 180px;
height: 180px;
padding: 10px;
background: #fff;
flex-direction: column;
.qrcode-label-title {
margin-bottom: 10px;
font-size: 14px;
font-weight: bold;
}
.qrcode-label-img {
width: 140px;
height: 140px;
}
}
</style>
<style lang="scss">
.customInput {
.el-input__inner {
-webkit-text-security: disc !important;
}
}
</style>