This commit is contained in:
stt
2025-12-08 08:54:48 +08:00
7 changed files with 443 additions and 207 deletions

View File

@@ -1,22 +1,31 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 查询设备数据趋势 // 查询设备数据趋势
export function getDeviceDataTrend(data: any) { export function getDeviceDataTrend(data: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/datatrend/querydatatrend', url: '/cs-harmonic-boot/datatrend/querydatatrend',
method: 'POST', method: 'POST',
data data
}) })
} }
// 波形下载 // 波形下载
export function getFileZip(params: any) { export function getFileZip(params: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/event/getFileZip', url: '/cs-harmonic-boot/event/getFileZip',
method: 'get', method: 'get',
params, params,
responseType: 'blob' responseType: 'blob'
}) })
} }
export function exportModel(data: any) {
return createAxios({
url: '/cs-harmonic-boot/exportmodel/exportModel',
method: 'post',
data: data,
responseType: 'blob'
})
}

BIN
src/assets/img/jss.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View File

@@ -102,8 +102,8 @@ const tableStore: any = new TableStore({
}, },
legend: { legend: {
orient: 'vertical', orient: 'vertical',
top: 'center', top: '50',
right: '5%', right: '10',
formatter: function (name: string) { formatter: function (name: string) {
const item = chartData.value.find((i: any) => i.name === name) const item = chartData.value.find((i: any) => i.name === name)
return item ? `${name} ${item.value}` : name return item ? `${name} ${item.value}` : name

View File

@@ -106,8 +106,8 @@ const eventEcharts = () => {
}, },
legend: { legend: {
orient: 'vertical', orient: 'vertical',
top: 'center', top: '50',
right: '5%', right: '10',
formatter: function (e: any) { formatter: function (e: any) {
return e + ' ' + data.value.filter((item: any) => item.name == e)[0].value + '次' return e + ' ' + data.value.filter((item: any) => item.name == e)[0].value + '次'
} }

View File

@@ -1,181 +1,196 @@
<template> <template>
<div> <div>
<div style="transition: all 0.3s; overflow: hidden; height: 100%"> <div style="transition: all 0.3s; overflow: hidden; height: 100%">
<div class="cn-tree">
<div class="cn-tree"> <div style="display: flex; align-items: center" class="mb10">
<div style="display: flex; align-items: center" class="mb10"> <el-input
<el-input maxlength="32" show-word-limit v-model.trim="filterText" placeholder="请输入内容" clearable> maxlength="32"
<template #prefix> show-word-limit
<Icon name="el-icon-Search" style="font-size: 16px" /> v-model.trim="filterText"
</template> placeholder="请输入内容"
</el-input> clearable
</div> >
<el-tree style="flex: 1; overflow: auto" :props="defaultProps" highlight-current <template #prefix>
:filter-node-method="filterNode" node-key="id" v-bind="$attrs" default-expand-all :data="tree" <Icon name="el-icon-Search" style="font-size: 16px" />
ref="treRef" @node-click="clickNode" :expand-on-click-node="false"> </template>
<template #default="{ node, data }"> </el-input>
<span class="custom-tree-node"> </div>
<div class="left"> <el-tree
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }" style="flex: 1; overflow: auto"
v-if="data.icon" /> :props="defaultProps"
<span>{{ node.label }}</span> highlight-current
</div> :filter-node-method="filterNode"
node-key="id"
</span> v-bind="$attrs"
</template> default-expand-all
</el-tree> :data="tree"
</div> ref="treRef"
</div> @node-click="clickNode"
</div> :expand-on-click-node="false"
</template> >
<template #default="{ node, data }">
<script lang="ts" setup> <span class="custom-tree-node">
import { ref, nextTick, watch, defineProps, defineEmits } from 'vue' <div class="left" style="display: flex; align-items: center">
import { getSchemeTree, getTestRecordInfo } from '@/api/cs-device-boot/planData' <Icon
import { useConfig } from '@/stores/config' :name="data.icon"
import useCurrentInstance from '@/utils/useCurrentInstance' style="font-size: 16px"
import { ElTree } from 'element-plus' :style="{ color: data.color }"
import { getTemplateByDept } from '@/api/harmonic-boot/luckyexcel' v-if="data.icon"
import { useDictData } from '@/stores/dictData' />
defineOptions({ <span style="margin-left: 5px;">{{ node.label }}</span>
name: 'govern/schemeTree' </div>
}) </span>
</template>
interface Props { </el-tree>
template?: boolean </div>
</div>
} </div>
const dictData = useDictData() </template>
const props = withDefaults(defineProps<Props>(), {
template: false, <script lang="ts" setup>
import { ref, nextTick, watch, defineProps, defineEmits } from 'vue'
}) import { getSchemeTree, getTestRecordInfo } from '@/api/cs-device-boot/planData'
const filterText = ref('') import { useConfig } from '@/stores/config'
watch(filterText, val => { import useCurrentInstance from '@/utils/useCurrentInstance'
treRef.value!.filter(val) import { ElTree } from 'element-plus'
}) import { getTemplateByDept } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData'
const filterNode = (value: string, data: any, node: any) => { defineOptions({
if (!value) return true name: 'govern/schemeTree'
// return data.name.includes(value) })
if (data.name) {
return chooseNode(value, data, node) interface Props {
} template?: boolean
} }
const chooseNode = (value: string, data: any, node: any) => { const dictData = useDictData()
if (data.name.indexOf(value) !== -1) { const props = withDefaults(defineProps<Props>(), {
return true template: false
} })
const level = node.level const filterText = ref('')
// 如果传入的节点本身就是一级节点就不用校验了 watch(filterText, val => {
if (level === 1) { treRef.value!.filter(val)
return false })
}
// 先取当前节点的父节点 const filterNode = (value: string, data: any, node: any) => {
let parentData = node.parent if (!value) return true
// 遍历当前节点的父节点 // return data.name.includes(value)
let index = 0 if (data.name) {
while (index < level - 1) { return chooseNode(value, data, node)
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤 }
if (parentData.data.name.indexOf(value) !== -1) { }
return true const chooseNode = (value: string, data: any, node: any) => {
} if (data.name.indexOf(value) !== -1) {
// 否则的话再往上一层做匹配 return true
parentData = parentData.parent }
index++ const level = node.level
} // 如果传入的节点本身就是一级节点就不用校验了
// 没匹配到返回false if (level === 1) {
return false return false
} }
/** 树形结构数据 */ // 先取当前节点的父节点
const defaultProps = { let parentData = node.parent
children: 'children', // 遍历当前节点的父节点
label: 'name', let index = 0
value: 'id' while (index < level - 1) {
} // 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) {
return true
const emit = defineEmits(['init', 'checkChange', 'nodeChange', 'editNode', 'getChart', 'Policy']) }
const config = useConfig() // 否则的话再往上一层做匹配
const tree = ref() parentData = parentData.parent
const treRef = ref() index++
const id: any = ref(null) }
const treeData = ref({}) // 没匹配到返回false
//获取方案树形数据 return false
const getTreeList = () => { }
getSchemeTree().then(res => { /** 树形结构数据 */
let arr: any[] = [] const defaultProps = {
children: 'children',
res.data.forEach((item: any) => { label: 'name',
item.icon = 'el-icon-Menu' value: 'id'
item.color = config.getColorVal('elementUiPrimary') }
item?.children.forEach((item2: any) => {
item2.icon = 'el-icon-Document' const emit = defineEmits(['init', 'checkChange', 'nodeChange', 'editNode', 'getChart', 'Policy'])
item2.color = config.getColorVal('elementUiPrimary') const config = useConfig()
arr.push(item2) const tree = ref()
}) const treRef = ref()
}) const id: any = ref(null)
tree.value = res.data const treeData = ref({})
nextTick(() => { //获取方案树形数据
if (arr.length) { const getTreeList = () => {
treRef.value.setCurrentKey(id.value || arr[0].id) getSchemeTree().then(res => {
let list = id.value ? arr.find((item: any) => item.id == id.value) : arr[0] let arr: any[] = []
// 注册父组件事件
emit('init', { res.data.forEach((item: any) => {
level: 2, item.icon = 'el-icon-Menu'
...list item.color = config.getColorVal('elementUiPrimary')
}) item?.children.forEach((item2: any) => {
} else { item2.icon = 'el-icon-Document'
emit('init') item2.color = config.getColorVal('elementUiPrimary')
} arr.push(item2)
}) })
}) })
} tree.value = res.data
nextTick(() => {
//方案id if (arr.length) {
const planId: any = ref('') treRef.value.setCurrentKey(id.value || arr[0].id)
let list = id.value ? arr.find((item: any) => item.id == id.value) : arr[0]
const clickNode = (e: anyObj) => { // 注册父组件事件
e?.children ? (planId.value = e.id) : (planId.value = e.pid) emit('init', {
id.value = e.id level: 2,
emit('nodeChange', e) ...list
} })
} else {
emit('init')
if (props.template) { }
getTemplateByDept({ id: dictData.state.area[0].id }).then((res: any) => { })
emit('Policy', res.data) })
getTreeList() }
}).catch(err => {
getTreeList() //方案id
}) const planId: any = ref('')
} else {
getTreeList() const clickNode = (e: anyObj) => {
} e?.children ? (planId.value = e.id) : (planId.value = e.pid)
id.value = e.id
</script> emit('nodeChange', e)
<style lang="scss" scoped> }
.cn-tree {
flex-shrink: 0; if (props.template) {
display: flex; getTemplateByDept({ id: dictData.state.area[0].id })
flex-direction: column; .then((res: any) => {
box-sizing: border-box; emit('Policy', res.data)
padding: 10px; getTreeList()
height: 100%; })
width: 100%; .catch(err => {
height: calc(100vh - 125px); getTreeList()
overflow-y: auto; })
} else {
:deep(.el-tree) { getTreeList()
border: 1px solid var(--el-border-color); }
} </script>
<style lang="scss" scoped>
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) { .cn-tree {
background-color: var(--el-color-primary-light-7); flex-shrink: 0;
} display: flex;
flex-direction: column;
.menu-collapse { box-sizing: border-box;
color: var(--el-color-primary); padding: 10px;
} height: 100%;
} width: 100%;
</style> height: calc(100vh - 125px);
overflow-y: auto;
:deep(.el-tree) {
border: 1px solid var(--el-border-color);
}
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
background-color: var(--el-color-primary-light-7);
}
.menu-collapse {
color: var(--el-color-primary);
}
}
</style>

View File

@@ -855,6 +855,31 @@
<el-option label="已治理" :value="1" /> <el-option label="已治理" :value="1" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item
class="form-item"
label="运行状态:"
:rules="{ required: true, message: '请选择运行状态', trigger: 'blur' }"
>
<!-- 0运行1检修2停运3调试4退运 -->
<el-select
filterable
v-model="lineItem.runStatus"
placeholder="请选择运行状态"
:disabled="
!(
(nodeLevel == 4 && pageStatus == 3) ||
((nodeLevel == 3 || (nodeLevel == 2 && pageStatus == 2)) &&
pageStatus == 2)
)
"
>
<el-option label="运行" :value="0" />
<el-option label="检修" :value="1" />
<el-option label="停运" :value="2" />
<el-option label="调试" :value="3" />
<el-option label="退运" :value="4" />
</el-select>
</el-form-item>
</div> </div>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@@ -1059,6 +1084,7 @@ interface LineInfo {
monitorUser: string monitorUser: string
monitorObj: string monitorObj: string
govern: string | number govern: string | number
runStatus: string | number
basicCapacity: number basicCapacity: number
shortCircuitCapacity: number shortCircuitCapacity: number
devCapacity: number devCapacity: number
@@ -1437,6 +1463,7 @@ const add = () => {
devMac: '', devMac: '',
monitorObj: '', monitorObj: '',
govern: 0, govern: 0,
runStatus: 0,
basicCapacity: 0, basicCapacity: 0,
shortCircuitCapacity: 0, shortCircuitCapacity: 0,
devCapacity: 0, devCapacity: 0,
@@ -1612,6 +1639,7 @@ const updateLineFunc = (id: any) => {
monitorUser: currentLine.monitorUser || '', monitorUser: currentLine.monitorUser || '',
monitorObj: currentLine.monitorObj || '', monitorObj: currentLine.monitorObj || '',
govern: currentLine.govern , govern: currentLine.govern ,
runStatus: currentLine.runStatus ,
basicCapacity: currentLine.basicCapacity || 0, basicCapacity: currentLine.basicCapacity || 0,
shortCircuitCapacity: currentLine.shortCircuitCapacity || 0, shortCircuitCapacity: currentLine.shortCircuitCapacity || 0,
devCapacity: currentLine.devCapacity || 0, devCapacity: currentLine.devCapacity || 0,
@@ -1805,6 +1833,7 @@ const next = async () => {
devMac: '', devMac: '',
monitorObj: '', monitorObj: '',
govern: 0, govern: 0,
runStatus: 0,
basicCapacity: 0, basicCapacity: 0,
shortCircuitCapacity: 0, shortCircuitCapacity: 0,
devCapacity: 0, devCapacity: 0,
@@ -2215,6 +2244,7 @@ const resetAllForms = () => {
line.monitorUser = '' line.monitorUser = ''
line.monitorObj = '' line.monitorObj = ''
line.govern = 0 line.govern = 0
line.runStatus = 0
line.basicCapacity = 0 line.basicCapacity = 0
line.shortCircuitCapacity = 0 line.shortCircuitCapacity = 0
line.devCapacity = 0 line.devCapacity = 0
@@ -2381,6 +2411,7 @@ const submitData = () => {
monitorUser: currentLine.monitorUser, monitorUser: currentLine.monitorUser,
monitorObj: currentLine.monitorObj, monitorObj: currentLine.monitorObj,
govern: currentLine.govern, govern: currentLine.govern,
runStatus: currentLine.runStatus,
basicCapacity: currentLine.basicCapacity, basicCapacity: currentLine.basicCapacity,
shortCircuitCapacity: currentLine.shortCircuitCapacity, shortCircuitCapacity: currentLine.shortCircuitCapacity,
devCapacity: currentLine.devCapacity, devCapacity: currentLine.devCapacity,
@@ -2567,6 +2598,7 @@ const handleLineTabsEdit = (targetName: any, action: any) => {
monitorUser: '', monitorUser: '',
monitorObj: '', monitorObj: '',
govern: 0, govern: 0,
runStatus: 0,
basicCapacity: 0, basicCapacity: 0,
shortCircuitCapacity: 0, shortCircuitCapacity: 0,
devCapacity: 0, devCapacity: 0,

View File

@@ -0,0 +1,180 @@
<template>
<div class="default-main" :style="height">
<splitpanes style="height: 100%" class="default-theme" id="navigation-splitpanes">
<pane :size="size">
<!-- <pointTreeWx
:default-expand-all="false"
template
@node-click="handleNodeClick"
@init="handleNodeClick"
></pointTreeWx> -->
<CloudDeviceEntryTree
ref="TerminalRef"
@node-click="handleNodeClick"
@init="handleNodeClick"
></CloudDeviceEntryTree>
</pane>
<pane style="background: #fff" :style="height">
<TableHeader ref="TableHeaderRef" datePicker :show-search="false">
<template v-slot:select>
<el-form-item label="客户名称">
<el-input
v-model="tableStore.table.params.crmName"
maxlength="32"
show-word-limit
clearable
placeholder="请输入客户名称"
/>
</el-form-item>
<el-form-item label="报表编号">
<el-input
v-model="tableStore.table.params.reportNumber"
clearable
placeholder="请输入报表编号"
maxlength="12"
show-word-limit
/>
</el-form-item>
</template>
<template #operation>
<el-upload
:show-file-list="false"
ref="uploadRef"
action=""
accept=".png,.jpg"
:on-change="choose"
:auto-upload="false"
>
<template #trigger>
<el-button icon="el-icon-Upload" type="primary" class="mr10 ml10">上传接线图</el-button>
</template>
</el-upload>
<el-button icon="el-icon-Download" type="primary" @click="exportEvent">生成</el-button>
</template>
</TableHeader>
<div class="box">
<div id="luckysheet">
<img
width="100%"
:style="`height: calc(${tableStore.table.height} + 40px)`"
src="@/assets/img/jss.png"
/>
</div>
</div>
</pane>
</splitpanes>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, provide } from 'vue'
import 'splitpanes/dist/splitpanes.css'
import { Splitpanes, Pane } from 'splitpanes'
import TableStore from '@/utils/tableStore'
import PointTree from '@/components/tree/pqs/pointTree.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { mainHeight } from '@/utils/layout'
import pointTreeWx from '@/components/tree/govern/pointTreeWx.vue'
import { genFileId, ElMessage, ElNotification } from 'element-plus'
import type { UploadProps, UploadUserFile } from 'element-plus'
import CloudDeviceEntryTree from '@/components/tree/govern/cloudDeviceEntryTree.vue'
import { exportModel } from '@/api/cs-harmonic-boot/datatrend'
defineOptions({
// name: 'harmonic-boot/report/word'
})
const height = mainHeight(20)
const size = ref(0)
const dictData = useDictData()
const TableHeaderRef = ref()
const dotList: any = ref({})
const Template: any = ref({})
const uploadList: any = ref([])
const tableStore = new TableStore({
url: '',
method: 'POST',
column: [],
beforeSearchFun: () => {},
loadCallback: () => {}
})
provide('tableStore', tableStore)
onMounted(() => {
const dom = document.getElementById('navigation-splitpanes')
if (dom) {
size.value = Math.round((180 / dom.offsetHeight) * 100)
}
})
const handleNodeClick = (data: any, node: any) => {
dotList.value = data
}
// 上传
const choose = (files: any) => {
const isJPG = files.raw.type === 'image/jpg'
const isJPEG = files.raw.type === 'image/jpeg'
const isPNG = files.raw.type === 'image/png'
if (!isJPG && !isPNG && !isJPEG) {
ElMessage.warning('上传文件只能是 JPG/PNG 格式!')
return false
}
uploadList.value = files
ElMessage.success('上传成功')
}
// 生成
const exportEvent = () => {
console.log("🚀 ~ exportEvent ~ dotList.value:", dotList.value)
if (dotList.value?.level != 4) {
return ElMessage.warning('请选择监测点进行报告生成!')
}
let form = new FormData()
form.append('lineIndex', dotList.value.id)
form.append('name', dotList.value.name)
form.append('crmName', tableStore.table.params.crmName || '')
form.append('reportNumber', tableStore.table.params.reportNumber || '')
form.append('type', '0')
form.append('startTime', TableHeaderRef.value.datePickerRef.timeValue[0])
form.append('endTime', TableHeaderRef.value.datePickerRef.timeValue[1])
console.log('🚀 ~ exportEvent ~ uploadList.value:', uploadList.value?.raw)
form.append('file', uploadList.value?.raw || '')
// 特殊字符正则表达式
const specialCharRegex = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/
if (
specialCharRegex.test(tableStore.table.params.crmName) ||
specialCharRegex.test(tableStore.table.params.reportNumber)
) {
ElNotification({
type: 'error',
message: '包含特殊字符,请注意修改!'
})
} else {
ElMessage('生成报告中...')
exportModel(form).then((res: any) => {
let blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=UTF-8'
})
// createObjectURL(blob); //创建下载的链接
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a') // 创建a标签
link.href = url
link.download = dotList.value.name // 设置下载的文件名
document.body.appendChild(link)
link.click() //执行下载
document.body.removeChild(link)
})
}
}
</script>
<style lang="scss">
.splitpanes.default-theme .splitpanes__pane {
background: #eaeef1;
}
.box {
padding: 10px;
}
</style>