光字牌开发

dev_xq_0.0.1
谢庆 5 hours ago
parent 05cff73389
commit 865045c4fe

Binary file not shown.

@ -62,8 +62,11 @@ import VueThreeBuilding from '@/components/three-components/vue-three-building.v
import VueExtinguisherTable from '@/components/vue-components/vue-extinguisher-table.vue';
import VueIlluminationTable from '@/components/vue-components/vue-illumination-table.vue';
import VueStrInfo from '@/components/vue-components/vue-str-info.vue';
// src\components\vue-components\vue-test-controller.vue
import VueTestController from '@/components/vue-components/vue-test-controller.vue';
import VueCar from '@/components/vue-components/vue-car.vue';
import VueBaseInformation from '@/components/vue-components/vue-base-information.vue';
// F:\vue\workspace\maotu-webtopo\src\components\vue-components\vue-3d-base-information.vue
import Vue3dBaseInformation from '@/components/vue-components/vue-3d-base-information.vue';
//
import VueGradeGauge from '@/components/vue-components/echarts-grade-gauge.vue';
@ -120,6 +123,9 @@ instance?.appContext.app.component('vue-my-extinguisher-table', VueExtinguisherT
instance?.appContext.app.component('vue-my-illumination-table', VueIlluminationTable);
instance?.appContext.app.component('vue-my-str-info', VueStrInfo);
instance?.appContext.app.component('vue-my-test-controller', VueTestController);
instance?.appContext.app.component('vue-my-car', VueCar);
instance?.appContext.app.component('vue-my-base-information', VueBaseInformation);
instance?.appContext.app.component('vue-my-3d-base-information', Vue3dBaseInformation);
instance?.appContext.app.component('vue-grade-gauge', VueGradeGauge);
instance?.appContext.app.component('vue-guage-line-chart', VueGuageLineChart);
@ -448,8 +454,18 @@ leftAsideStore.registerConfig('vue公共组件', [
},
testColor: {
type: 'color',
val: '#44B6E7',
val: '#ffffff',
title: '文字颜色'
},
fontSize: {
type: 'number',
val: 16,
title: '字体大小'
},
fontBold: {
type: 'switch',
val: false,
title: '加粗'
}
}
},
@ -721,6 +737,97 @@ leftAsideStore.registerConfig('vue公共组件', [
]
}
}
},
{
id: 'vue-my-car',
title: 'vue卡片组件',
type: 'vue',
thumbnail: '/svgs/table-base.svg',
props: {
fontFamily: {
title: '字体',
type: 'select',
val: 'Segoe UI',
options: [
{
value: 'Segoe UI',
label: 'Segoe UI'
},
{
value: '微软雅黑',
label: '微软雅黑'
},
{
value: '黑体',
label: '黑体'
},
{
value: '宋体',
label: '宋体'
}
]
},
testContent: {
type: 'input',
val: '标题内容',
title: '标题内容'
},
fontSize: {
type: 'number',
val: 14,
title: '标题大小'
},
testColor: {
type: 'color',
val: '#ffffff',
title: '文字颜色'
},
skeletonBool: {
type: 'switch',
val: false,
title: '辅助框'
},
skeletonRows: {
type: 'number',
val: 3,
title: '框数量'
}
}
},
{
id: 'vue-my-base-information',
title: 'vue基本信息',
type: 'vue',
thumbnail: '/svgs/table-base.svg',
props: {
dataSource: {
title: '数据源',
type: 'select',
val: '--',
options: [
{
value: 'fireAlarmHost',
label: '火灾报警系统主机'
},
{
value: 'host',
label: '主机'
},
{
value: 'oilChromatography',
label: '2号主变油色谱'
},
{
value: 'partialDischarge',
label: '2号主变局放'
},
{
value: 'switchGap',
label: '2215开关间隔'
}
]
}
}
}
]);
@ -957,6 +1064,54 @@ leftAsideStore.registerConfig('vue3D组件', [
val: '--'
}
}
},
{
id: 'vue-my-3d-base-information',
title: '3d-基本信息',
type: 'vue',
thumbnail: '/svgs/table-only.svg',
props: {
dataTitle: {
title: '标题',
type: 'input',
val: '单个设备基本信息'
},
dataSource: {
title: '数据源',
type: 'select',
val: '--',
options: [
{
value: 'smokeSiren',
label: '烟感报警器'
},
{
value: 'temperatureFireDetector',
label: '温感火灾探测器'
},
{
value: 'fireHydrant',
label: '消火栓'
},
{
value: 'fireExtinguisher',
label: '灭火器'
},
{
value: 'evacuationSign',
label: '疏散指示灯'
},
{
value: 'infraredBeam',
label: '红外对射'
},
{
value: 'accessController',
label: '门禁控制器'
}
]
}
}
}
]);

Binary file not shown.

@ -73,8 +73,8 @@
</el-icon> </el-button
></el-button-group>
<!-- <el-divider direction="vertical"></el-divider> -->
<!-- <el-popover
<el-divider direction="vertical"></el-divider>
<el-popover
placement="bottom"
:width="240"
trigger="hover"
@ -131,7 +131,7 @@
</el-button>
</el-button-group>
</div>
</el-popover> -->
</el-popover>
<!-- <el-divider direction="vertical"></el-divider> -->
@ -332,6 +332,11 @@ const headerPanelProps = withDefaults(defineProps<HeaderPanelProps>(), {
useThumbnail: false,
selectedItemsId: () => []
});
function leftJustify() {
emits('alignSelected', 'left');
}
const emits = defineEmits([
'update:leftAside',
'update:rightAside',

@ -348,6 +348,7 @@ const onRenderCoreMouseDown = (item: IDoneJson, e: MouseEvent) => {
globalStore.refreshSelectedItemsId();
}
};
const onMouseDown = (e: MouseEvent) => {
beginListenerKeyDown();
globalStore.cancelAllSelect();
@ -376,6 +377,7 @@ const onMouseDown = (e: MouseEvent) => {
globalStore.setIntention('beginMulSelect');
selectedAreaRef.value?.onMouseDown(e);
};
/**
* 区域选择结束事件 之所以用getBoundingClientRect是为了处理旋转后的坐标
* @param area_binfo 区域选择的边界信息

@ -441,7 +441,7 @@ const props2 = {
};
async function getDataSource() {
debugger
debugger;
let cls = '-1';
if (selectedPartition.value && selectedPartition.value.length > 0)
cls = selectedPartition.value[selectedPartition.value.length - 1];
@ -458,19 +458,19 @@ async function getDataSource() {
gridData.value.splice(0, gridData.value.length);
response.data.forEach((item: any) => {
console.log('item:', item);
gridDataSource.value.push({
gridDataSource.value.push({
modeId: item.id,
name: item.name,
bType: item.btype
});
if (code == undefined || code == item.btype) {
gridData.value.push({
modeId: item.id,
name: item.name,
bType: item.btype
});
if (code == undefined || code == item.btype) {
gridData.value.push({
modeId: item.id,
name: item.name,
bType: item.btype
});
}
}
});
ElMessage.success('接口请求成功');

@ -1,20 +1,50 @@
<template>
<div style="width: 100%; height: 100%">
<h2 class="my-button" style="width: 100%; height: 100%">{{ props.modelValue }}</h2>
<!-- <h2 class="my-button" style="width: 100%; height: 100%">{{ props.modelValue }}</h2> -->
<el-text class="my-button">{{ props.modelValue }}</el-text>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
const props = defineProps({
modelValue: String,
fontFamily: String,
testColor: String
testColor: String,
fontSize: Number,
fontBold: Boolean
});
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {}
});
let computedBold = computed({
//
get() {
return props.fontBold ? 'bold' : 'normal';
}, //
set(val) {}
});
</script>
<style scoped>
.my-button {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
display: flex;
font-weight: v-bind('computedBold');
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
text-align: center;
color: v-bind('props.testColor');
font-family: v-bind('props.fontFamily');
font-size: v-bind('computedSize');
}
</style>

@ -0,0 +1,262 @@
<template>
<el-dialog
:model-value="modelValue"
@update:model-value="handleUpdate"
class="elDialog"
top="10vh"
title="组件绑定"
height="80%"
width="60%"
>
<div class="m-4">
<el-row>
<el-col :span="2"> 分区 </el-col>
<el-col :span="8">
<el-cascader
v-model="selectedPartition"
:options="options"
:props="props2"
clearable
@change="handlePartitionChange"
/>
</el-col>
<el-col :span="14">
<el-button type="primary" @click="getDataSource"></el-button>
</el-col>
</el-row>
</div>
<el-table :data="filteredData" :max-height="450">
<el-table-column type="index" label="序号" />
<el-table-column prop="modeId" label="遥ID" />
<el-table-column prop="name" label="名称" />
<el-table-column
v-if="inputTypeTagValue == undefined"
prop="bType"
label="类型筛选"
:filters="[
{ text: '遥信', value: 1 },
{ text: '遥测', value: 2 },
{ text: '遥控', value: 3 },
{ text: '遥调', value: 4 },
{ text: '其他', value: 0 }
]"
:filter-method="filterTag"
filter-placement="bottom-end"
filter-confirm-button-text="确定"
filter-reset-button-text="重置"
>
<template #default="scope">
<el-tag type="primary" v-if="scope.row.bType === 1">{{ '' }}</el-tag>
<el-tag type="success" v-else-if="scope.row.bType === 2">{{ '遥测' }}</el-tag>
<el-tag type="info" v-else-if="scope.row.bType === 3">{{ '遥控' }}</el-tag>
<el-tag type="warning" v-else-if="scope.row.bType === 4">{{ '遥调' }}</el-tag>
<el-tag type="danger" v-else>{{ '' }}</el-tag>
</template>
</el-table-column>
<el-table-column v-if="inputTypeTagValue != undefined" label="类型">
<template #default="scope">
<el-tag type="primary" v-if="scope.row.bType === 1">{{ '' }}</el-tag>
<el-tag type="success" v-else-if="scope.row.bType === 2">{{ '遥测' }}</el-tag>
<el-tag type="info" v-else-if="scope.row.bType === 3">{{ '遥控' }}</el-tag>
<el-tag type="warning" v-else-if="scope.row.bType === 4">{{ '遥调' }}</el-tag>
</template>
</el-table-column>
<!-- <el-table-column label="操作">
<template #default="scope">
<el-button type="primary" size="small" @click="handleEdit(scope.row)"> </el-button>
</template>
</el-table-column> -->
<el-table-column align="center">
<template #header>
<el-input v-model="search" size="small" placeholder="节点名称" />
</template>
<template #default="scope">
<el-button size="small" @click="console.log(scope.row)"> </el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</template>
<script setup lang="ts">
import {
ElInput,
ElButton,
ElMessage,
} from 'element-plus';
import { ref, computed, onMounted } from 'vue';
import { modelApi } from '@/utils/request';
interface GridDataItem {
modeId: string;
name: string;
bType: number;
}
const props = defineProps({
modelValue: {
type: Boolean,
required: true
}
});
const emit = defineEmits({
'update:modelValue': (value: boolean) => true
});
const handleUpdate = (value: boolean) => {
emit('update:modelValue', value);
};
//
const selectedPartition = ref<string[]>([]);
const gridDataSource = ref<GridDataItem[]>([]);
const gridData = ref<GridDataItem[]>([]);
const search = ref('');
let options: any[] = [];
const props2 = {
multiple: false,
checkStrictly: true
};
//
const handlePartitionChange = (value: string[]) => {
console.log('选中的分区数据:', value);
// value
// [' ID', ' ID', ...]
//
if (value && value.length > 0) {
const lastValue = value[value.length - 1];
console.log('最后选中的值:', lastValue);
//
}
};
//
const filteredData = computed(() => {
if (!search.value) {
return gridData.value;
}
return gridData.value.filter((item) => {
return item.name.toLowerCase().includes(search.value.toLowerCase());
});
});
let inputTypeTagValue = ref<string>();
async function getDataSource() {
let cls = '-1';
if (selectedPartition.value && selectedPartition.value.length > 0)
cls = selectedPartition.value[selectedPartition.value.length - 1];
let response = await modelApi.getNodeByCls_get(cls);
if (response.code != 200 || response.data == null) {
ElMessage.error(response.message);
} else {
let code: number | undefined;
setInputTagVal();
if (inputTypeTagValue.value !== undefined) code = getNameForNode(inputTypeTagValue.value);
gridDataSource.value.splice(0, gridDataSource.value.length);
gridData.value.splice(0, gridData.value.length);
response.data.forEach((item: any) => {
console.log('item:', item);
gridDataSource.value.push({
modeId: item.id,
name: item.name,
bType: item.btype
});
if (code == undefined || code == item.btype) {
gridData.value.push({
modeId: item.id,
name: item.name,
bType: item.btype
});
}
});
ElMessage.success('接口请求成功');
console.log('gridData:', gridData.value);
}
}
async function getAllTree() {
const response = await modelApi.allTree_get();
if (response.code != 200 || response.data == null) {
ElMessage.error(response.message);
} else {
// id value
options = convertTreeData(response.data);
ElMessage.success('树接口请求成功');
}
}
//
function convertTreeData(data: any[]): any[] {
return data.map((item) => ({
value: item.id.toString(), // id value
label: item.label,
children: item.children ? convertTreeData(item.children) : []
}));
}
//
function setInputTagVal() {
return '遥信';
// for (const key in selectItemPropsSettingProps.propsInfo) {
// const item = selectItemPropsSettingProps.propsInfo[key];
// if (item.type === 'inputTypeTag') {
// inputTypeTagValue.value = item.val;
// console.log('inputTypeTagValue:', inputTypeTagValue.value);
// break;
// }
// }
}
const filterTag = (value: number, row: any) => {
return row.bType === value;
};
function getNameForNode(nodeName: string): number {
switch (nodeName) {
case '遥信':
return 1;
case '遥测':
return 2;
case '遥控':
return 3;
case '遥调':
return 4;
default:
return 0;
}
}
onMounted(() => {
getAllTree();
});
</script>
<style lang="less" scoped>
.elDialog {
padding: 0;
margin: 0;
}
.demo-form-inline {
padding: 15px 0;
}
.demo-form-inline .el-input {
--el-input-width: 150px;
}
.demo-form-inline .el-select {
--el-select-width: 150px;
}
</style>

@ -0,0 +1,293 @@
<template>
<el-card class="card" shadow="hover" :body-style="{ padding: '10px' }">
<h2 class="cardHead">{{ dataTitle }}</h2>
<div class="cardBody">
<el-row :gutter="20" style="height: 100%">
<el-col :span="12" class="image-col">
<div ref="containerRef" class="container" />
</el-col>
<el-col :span="12">
<el-row v-for="item in dataObj" :key="item.field" class="info-row">
<el-col :span="10" class="info-label">{{ item.field }}</el-col>
<el-col :span="12"
><el-tag size="small">{{ item.value }}</el-tag></el-col
>
</el-row>
</el-col>
</el-row>
</div>
</el-card>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, reactive, computed, watch } from 'vue';
import {
AxesHelper,
Color,
PerspectiveCamera,
Scene,
WebGLRenderer,
AmbientLight,
DirectionalLight,
PointLight
} from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { invariant3DObjMap, type Paragraph } from '@/utils/invariable';
const containerRef = ref<HTMLDivElement>();
//
const scene = new Scene();
//
const camera = new PerspectiveCamera(
45,
containerRef.value?.clientWidth! / containerRef.value?.clientHeight!,
0.1,
1000
);
camera.position.set(-40, 20, 35);
camera.lookAt(scene.position);
//
const renderer = new WebGLRenderer({
antialias: true, // 齿
alpha: true, //
preserveDrawingBuffer: true //
});
renderer.setClearColor(new Color('#131519'));
renderer.setSize(containerRef.value?.clientWidth!, containerRef.value?.clientHeight!);
renderer.shadowMap.enabled = true;
renderer.setPixelRatio(window.devicePixelRatio || 1); //
//
const orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.autoRotate = false;
orbitControls.autoRotateSpeed = 5;
orbitControls.dampingFactor = 0.1;
orbitControls.enableDamping = true;
orbitControls.enableRotate = true;
orbitControls.minDistance = 10;
orbitControls.maxPolarAngle = Math.PI * 0.5;
let modelPath = '/models/default/default.glb';
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
loader.setDRACOLoader(dracoLoader);
//
let currentModel: any = null;
//
loader.load(modelPath, (gltf: any) => {
gltf.scene.scale.set(1, 1, 1);
scene.add(gltf.scene);
currentModel = gltf.scene;
});
//
const ambientLight = new AmbientLight('#ffffff', 1.8); // 0.6 1.0
scene.add(ambientLight);
//
const directionalLight = new DirectionalLight('#ffffff', 3.8); // 0.2 0.8
scene.add(directionalLight);
directionalLight.position.set(20, 20, 10);
//
const pointLight = new PointLight('#ffffff', 1.8, 1800); // 0.5 1.0
scene.add(pointLight);
pointLight.position.set(0, 40, 0);
function animate() {
requestAnimationFrame(animate);
orbitControls.update();
renderer.render(scene, camera);
}
// ResizeObserver
let resizeObserver: ResizeObserver | null = null;
onMounted(() => {
const container = containerRef.value!;
container.appendChild(renderer.domElement);
//
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
//
resizeObserver = new ResizeObserver(() => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
});
resizeObserver.observe(container);
animate();
});
function loadingModel() {
//
if (currentModel) {
scene.remove(currentModel);
//
currentModel.traverse((child: any) => {
if (child.geometry) child.geometry.dispose();
if (child.material) {
if (Array.isArray(child.material)) {
child.material.forEach((m: any) => m.dispose());
} else {
child.material.dispose();
}
}
});
currentModel = null;
}
//
loader.load(modelPath, (gltf: any) => {
gltf.scene.scale.set(1, 1, 1);
scene.add(gltf.scene);
currentModel = gltf.scene;
});
}
const props = defineProps({
dataTitle: {
type: String,
default: '--'
},
dataSource: {
type: String,
default: '--'
}
});
const dataObj = reactive<Paragraph[]>([]);
function getDataBySource() {
dataObj.length = 0;
modelPath = '/models/default/default.glb';
if (props.dataSource != null && props.dataSource !== '' && props.dataSource !== '--') {
const dataObjTemp = invariant3DObjMap[props.dataSource];
if (!dataObjTemp) {
loadingModel();
return;
}
dataObjTemp.forEach((item) => {
if (item.field == '3D模型') {
modelPath = item.value;
} else dataObj.push(item);
});
}
loadingModel();
}
watch(
() => props.dataSource,
(newVal, oldVal) => {
getDataBySource();
}
);
onUnmounted(() => {
//
if (currentModel) {
scene.remove(currentModel);
currentModel.traverse((child: any) => {
if (child.geometry) child.geometry.dispose();
if (child.material) {
if (Array.isArray(child.material)) {
child.material.forEach((m: any) => m.dispose());
} else {
child.material.dispose();
}
}
});
currentModel = null;
}
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
});
</script>
<style scoped>
.card {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
}
.card :deep(.el-card__body) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 10px;
}
.cardHead {
margin: 0 0 15px 0;
padding: 0;
font-size: 16px;
font-weight: bold;
text-align: center;
color: #ffffff;
flex-shrink: 0;
}
.cardBody {
width: 100%;
height: 100%;
}
/* el-row 高度继承 */
.cardBody :deep(.el-row) {
/* height: 100%; */
}
/* el-col 高度继承 */
.cardBody :deep(.el-col) {
/* height: 100%;
display: flex;
align-items: stretch; */
}
/* 图片列样式 */
.image-col {
height: 100%;
display: flex;
}
/* 探测器图片样式 */
.detector-image {
width: 100%;
height: 100%;
object-fit: contain;
}
/* 信息行样式 */
.info-row {
margin-bottom: 1%;
}
/* 标签样式 */
.info-label {
font-size: 12px;
color: #ffffff;
}
/* el-tag 样式 */
.info-row :deep(.el-tag) {
font-size: 11px;
}
</style>

@ -0,0 +1,101 @@
<template>
<el-card class="card">
<h2 class="cardHead">基本信息</h2>
<div class="cardBody">
<el-scrollbar>
<el-row
v-for="item in dataObj"
:key="item.field"
:gutter="0"
style="display: flex; align-items: center; margin-bottom: 1%"
>
<el-col :span="8" :offset="4">{{ item.field }}</el-col>
<el-col :span="4">
<el-tag type="primary" size="small">{{ item.value }}</el-tag>
</el-col>
</el-row>
<!-- <el-row :gutter="0" style="display: flex; align-items: center;margin-bottom: 1%;">
<el-col :span="8" :offset="4"> 名称 </el-col>
<el-col :span="4">
<el-tag type="primary" size="small">{{ data.name }}</el-tag>
</el-col>
</el-row> -->
</el-scrollbar>
</div>
</el-card>
</template>
<script lang="ts" setup>
import { computed, ref, watch, onMounted, reactive } from 'vue';
import { invariantObjMap, type Paragraph } from '@/utils/invariable';
import { da } from 'element-plus/es/locales.mjs';
const props = defineProps({
dataSource: {
type: String,
default: '--'
}
});
const dataObj = reactive<Paragraph[]>([]);
function getDataBySource() {
dataObj.length = 0;
if (props.dataSource != null && props.dataSource !== '' && props.dataSource !== '--') {
const dataObjTemp = invariantObjMap[props.dataSource];
if (!dataObjTemp) return;
dataObj.push(...dataObjTemp);
}
}
onMounted(() => {
loadDataSource(props.dataSource);
});
watch(
() => props.dataSource,
(newVal, oldVal) => {
loadDataSource(newVal);
}
);
function loadDataSource(val: string) {
//
getDataBySource();
}
</script>
<style scoped>
:deep(.el-card__body) {
padding: 0;
padding-top: 10px;
padding-left: 2%;
}
.el-row .el-col {
margin-bottom: 6px; /* 设置上下间距 */
}
.card {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
color: #ffffff;
font-size: 12px;
}
.cardHead {
margin: 0;
padding: 0px;
padding-bottom: 5px;
text-align: center;
}
.cardBody {
width: 98%;
margin: 0;
padding: 0;
height: calc(100% - 40px);
overflow: hidden;
}
</style>

@ -0,0 +1,98 @@
<template>
<!-- <el-button @click="ccc">Default</el-button> -->
<el-card class="card">
<el-scrollbar height="100%">
<h2 class="cardHead">{{ testContent }}</h2>
<div class="cardBody" v-if="skeletonBool">
<el-skeleton :rows="rows" style="margin-top: 4%" />
</div>
</el-scrollbar>
</el-card>
</template>
<script lang="ts" setup>
import { computed, ref, watch, onMounted } from 'vue';
const props = defineProps({
fontFamily: {
type: String,
default: 'Segoe UI'
},
fontSize: {
type: Number,
default: 14
},
testColor: {
type: String,
default: '#000000'
},
testContent: {
type: String,
default: '标题内容'
},
skeletonBool: {
type: Boolean,
default: true
},
skeletonRows: {
type: Number,
default: 5
}
});
let computedSize = computed({
//
get() {
return props.fontSize + 'px';
}, //
set(val) {
console.log('有人修改了fullName', val);
}
});
let rows = computed({
//
get() {
if (props.skeletonRows >= 1) return props.skeletonRows - 1;
else return 0;
},
set(val) {}
});
</script>
<style scoped>
:deep(.el-card__body) {
padding: 0;
padding-top: 10px;
/* padding-left: 10px; */
}
.card {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
/* max-width: 480px; */
color: v-bind('props.testColor');
font-family: v-bind('props.fontFamily');
/* font-family: 'Segoe UI'; */
font-size: v-bind('computedSize');
}
.cardHead {
margin: 0;
padding: 0px;
padding-bottom: 5px;
text-align: center;
}
.cardBody {
width: 90%;
height: 100%;
margin-left: 5%;
}
/* 让骨架屏的段落更厚且宽度一致 */
:deep(.el-skeleton__item) {
height: 24px; /* 增加段落高度,默认约 16px */
width: 100%; /* 让所有段落宽度一致,填满容器 */
}
</style>

@ -1,6 +1,6 @@
<template>
<!-- 四遥遥测 -->
<button @click="console.log('data:', nodeByModelsStore.nodeMap)">点击</button>
<!-- <button @click="console.log('data:', nodeByModelsStore.nodeMap)">点击</button> -->
<el-row :gutter="20" width="100%">
<el-col :span="24" justify="center" align="middle">
<el-tag :type="color" effect="plain" style="width: 100%">

@ -839,6 +839,33 @@ export const constantRoutes: Readonly<RouteRecordRaw[]> = [
}
]
},
// F:\vue\workspace\maotu-webtopo\src\views\preview\lightBoard\index.vue
{
path: '/lightBoard', //F:\vue\workspace\maotu-webtopo\src\views\preview\classify\index.vue
name: 'lightBoard',
component: () => import('@/layout/view_index.vue'),
meta: {
title: '光字牌项目',
hidden: false,
treeHidden: false,
bottomHidden: false,
menuIcon: 'Operation'
},
children: [
{
path: '/lightBoard/index',
name: 'lightBoardIndex',
component: () => import('@/views/preview/lightBoard/index.vue'),
meta: {
title: '光字牌',
hidden: false,
treeHidden: false,
bottomHidden: false,
menuIcon: 'Setting'
}
}
]
},
{
path: '/test', //F:\vue\workspace\maotu-webtopo\src\views\preview\systemInfo\index.vue
name: 'test',

@ -0,0 +1,162 @@
export interface Paragraph {
field: string;
value: string;
}
export const invariantObjMap: Record<string, Paragraph[]> = {
// 火灾报警系统主机
fireAlarmHost: [
{ field: '名称', value: '火灾报警系统主机' },
{ field: '产品型号', value: 'JB-QB-GST200' },
{ field: '厂商', value: '海湾公司' },
{ field: '运行日期', value: '2023-04-01' },
{ field: '电源电压', value: '220V AC' },
{ field: '频率', value: '50Hz' },
{ field: '输出电压', value: '24V DC' },
{ field: '输出电流', value: '5A' },
{ field: '环境温度', value: '-5℃ ~ 45℃' },
{ field: '相对湿度', value: '5% ~ 95%' },
{ field: '维护单位', value: '陕西消防科技有限公司' },
{ field: '联系电话', value: '188*****00' }
],
// 主机
host: [
{ field: '名称', value: 'PAVLN排油注氮智能防护系统控制主机' },
{ field: '型号', value: 'JB-QB-GST200' },
{ field: '厂家', value: '海湾公司' },
{ field: '运行日期', value: '2015-10-10' },
{ field: '电源电压', value: 'DC 220V 5A/AC 220V 5A' },
{ field: '频率', value: '27~1600MHz' },
{ field: '输出电压', value: '24V DC' },
{ field: '输出电流', value: '5A' },
{ field: '环境温度', value: '-5℃ ~ 45℃' },
{ field: '相对湿度', value: '5% ~ 95%' },
{ field: '维护单位', value: '陕西消防科技有限公司' },
{ field: '联系电话', value: '188*****00' }
],
// 2号主变油色谱
oilChromatography: [
{ field: '厂家', value: '宁波理工' },
{ field: '型号', value: 'MGA2000-6H' },
{ field: '投运日期', value: '2015年10月27日' },
{ field: '工作电源', value: 'AC220V,50Hz' },
{ field: '工作环境温度', value: '-40°C~+80°C' },
{ field: '工作相对湿度', value: '5%~95%' }
],
//2号主变局放
partialDischarge: [
{ field: '厂家', value: '上海思瑞在线监测技术有限公司' },
{ field: '型号', value: 'MGA2000-6H' },
{ field: '投运日期', value: '2015年10月16日' },
{ field: '局放测量通道', value: '3个局放测量通道+1个噪音测量通道' },
{ field: '局放传感器频宽', value: '300MHZ~2000MHZ' },
{ field: '工作环境温度', value: '-25°C~+55°C' },
{ field: '工作相对湿度', value: '5%~95%' }
],
//2215开关间隔
switchGap: [
{ field: '厂家', value: '许继电气股份有限公司' },
{ field: '型号', value: 'DPD-801' },
{ field: '投运日期', value: '2015年11月17日' },
{ field: '局放测量通道', value: '3个局放测量通道+1个噪音测量通道' },
{ field: '工作环境温度', value: '-40°C~+70°C' },
{ field: '工作相对湿度', value: '5%~95%' }
]
};
const threeDPath = {
smokeSiren: '/models/smokeSiren/smokeSiren.glb',
default: '/models/default/default.glb'
};
export const invariant3DObjMap: Record<string, Paragraph[]> = {
// 烟感报警器
smokeSiren: [
{ field: '3D模型', value: threeDPath.smokeSiren },
{ field: '厂家', value: '海湾公司' },
{ field: '型号', value: 'VLP-666-CH标准型' },
{ field: '投运日期', value: '2014-06-08' },
{ field: '布设位置', value: '35kV高压室' },
{ field: '供电电压', value: '18-30VDC' },
{ field: 'IP等级', value: 'IP30' },
{ field: '接线方式', value: '两线制(L+、L-)' },
{ field: '尺寸(长x高x宽)', value: '350mmx225mmx125mm' },
{ field: '报警灵敏度范围', value: '0.005%至20%obs/m' },
{ field: '运行条件1', value: '探测器环境(-5°C至45°C)' },
{ field: '运行条件2', value: '采样空气(-20°C至60°C)' },
{ field: '运行条件3', value: '湿度(5%至95%RH)' }
],
// 温感火灾探测器
temperatureFireDetector: [
{ field: '3D模型', value: threeDPath.default },
{ field: '厂家', value: '海湾公司' },
{ field: '型号', value: 'CT1-155X' },
{ field: '投运日期', value: '2012-08-08' },
{ field: '使用环境', value: '温度-5°C~45°C' },
{ field: '火灾响应规模', value: '10mm' },
{ field: '测量温度精度', value: '≤1°C' },
{ field: '类型', value: '定温式' },
{ field: '报警温度', value: '85°C' },
{ field: '标准', value: 'GB16280-2014' }
],
// 消火栓
fireHydrant: [
{ field: '3D模型', value: threeDPath.default },
{ field: '厂家', value: '宏达消防设备制造有限公司' },
{ field: '型号', value: 'SN-65' },
{ field: '投运日期', value: '2016-10-10' },
{ field: '布设位置', value: '生产综合楼1楼走廊' },
{ field: '质量保证书类型', value: '型式认证' },
{ field: '质保证书', value: 'NO2015-2032' },
{ field: '维保单位', value: '陕西消防科技有限公司' },
{ field: '联系电话', value: '188*****00' }
],
// 灭火器
fireExtinguisher: [
{ field: '3D模型', value: threeDPath.default },
{ field: '厂家', value: '天河消防厂' },
{ field: '型号', value: 'MFZ35' },
{ field: '投运日期', value: '2012-08-08' },
{ field: '布设位置', value: '生产综合楼1楼35kV高压室' },
{ field: '名称', value: '推车式干粉灭火器' },
{ field: '品牌', value: '淮海' },
{ field: '灭火剂', value: '碳酸铵盐50%、硫酸铵25%、滑石粉25%' },
{ field: '灭火级别', value: 'ABCD' }
],
// 疏散指示灯
evacuationSign: [
{ field: '3D模型', value: threeDPath.default },
{ field: '厂家', value: 'XXX消防设备有限公司' },
{ field: '型号', value: 'BSD128F(自带应急照明)' },
{ field: '投运日期', value: '2013-08-06' },
{ field: '布设位置', value: '110kV保护室' },
{ field: '输入电压', value: 'AC220V(+10%)' },
{ field: '频率', value: '50Hz' },
{ field: '功率', value: '<3W' },
{ field: '应急时间', value: '>=90min' },
{ field: '转换时间', value: '<=10s' },
{ field: '充电时间', value: '>=24h' },
{ field: '电池类型', value: 'Ni-Cd 1.2v 800mAh' }
],
// 红外对射
infraredBeam: [
{ field: '3D模型', value: threeDPath.default },
{ field: '厂家', value: 'XX霍尼维尔' },
{ field: '生产型号', value: 'DT-8041' },
{ field: '供电电源', value: '9.0-15VDC' },
{ field: '微波频率', value: '10.525GHz' },
{ field: '工作温度', value: '-10°C~55°C' },
{ field: '相对湿度', value: '5至93%' },
{ field: '探测范围', value: '12m' }
],
// 门禁控制器
accessController: [
{ field: '3D模型', value: threeDPath.default },
{ field: '生产厂家', value: 'Pegasus' },
{ field: '生产型号', value: 'PP6750V' },
{ field: '投运日期', value: '2017-05-09' },
{ field: '电源电压', value: 'DC12V' },
{ field: '工作电流', value: '100mA' },
{ field: '管理门数', value: '4' }
]
};

@ -21,7 +21,21 @@ export const buildApiUrl = (path: string) => {
};
export const modelApi = {
// http://localhost:8080/data/tree/changeNodeClass
// http://localhost:8080/data/lightModel/getLightBoardList
getLightBoardList_post(endJson: any): Promise<ApiResponse> {
return fetch(buildApiUrl('/data/lightModel/getLightBoardList'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: '*/*'
},
body: JSON.stringify(endJson)
}).then((response) => {
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
});
},
changeNodeClass_post(endJson: any): Promise<ApiResponse> {
return fetch(buildApiUrl('/data/tree/changeNodeClass'), {
method: 'POST',
@ -36,12 +50,8 @@ export const modelApi = {
});
},
getNodeByCls_get(clsId: string): Promise<ApiResponse> {
return fetch(
buildApiUrl(`/data/tree/getNodeByCls?clsId=${clsId}`),
{
return fetch(buildApiUrl(`/data/tree/getNodeByCls?clsId=${clsId}`), {
method: 'GET',
headers: {
'Content-Type': 'application/json',

@ -389,7 +389,7 @@ const handleDelete = (data: Tree) => {
async function getNodeByCls(nodeClassId: string) {
let response = await modelApi.getNodeByCls_get(nodeClassId);
if (response.code == 200 && response.data != null) {
if (response.code == 200 && response.data != null) {
centerData.value.splice(0, centerData.value.length);
let globalDataRaw = (window as unknown as ExtendedParentWindow).globalData as Map<
string,
@ -409,13 +409,11 @@ async function getNodeByCls(nodeClassId: string) {
}
}
const handleNodeClick = (data: Tree) => {
console.log('点击节点:', data);
treeNodeId.value = data.id;
treeNodeName.value = data.label;
getNodeByCls(data.id);
};
const treeData = ref<Tree[]>([]);
@ -506,7 +504,7 @@ const initDraggable = () => {
onEnd: (evt: any) => {
console.log('右侧表格拖拽结束', evt);
if (!treeNodeId.value || treeNodeId.value==''){
if (!treeNodeId.value || treeNodeId.value == '') {
ElMessage.warning('请先选择树节点');
return;
}

@ -0,0 +1,247 @@
<template>
<div class="layout-container">
<el-row :gutter="10" style="margin-bottom: 2vh">
<el-col :span="24">
<el-card class="card-form">
<el-form :inline="true" class="form-inline" label-width="auto">
<el-form-item label="光字牌 ID">
<el-input v-model="formInline.lightBoardId" placeholder="光字牌 ID" clearable />
</el-form-item>
<el-form-item label="光字牌名称">
<el-input v-model="formInline.lightBoardName" placeholder="光字牌名称" clearable />
</el-form-item>
<el-form-item style="margin-left: auto">
<el-button type="primary">清除</el-button>
<el-button type="primary">查询</el-button>
</el-form-item>
</el-form>
</el-card>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="24">
<el-card class="card-table">
<el-button @click="getLightBoardList" style="margin-bottom: 10px">刷新</el-button>
<div style="height: calc(85%)">
<el-table
:data="lightBoardList"
:border="true"
:preserve-expanded-content="preserveExpanded"
:tooltip-formatter="tableRowFormatter"
show-overflow-tooltip
style="width: 100%; height: 500px"
>
<el-table-column type="expand">
<template #default="props">
<div m="4">
<p style="display: flex; align-items: center;">实际绑定节点<el-link style="margin: 0;padding: 0;" type="primary">{{ props.row.lightBoardStartNum-1 }}</el-link></p>
<el-button @click="bindStatus(props.row)"></el-button>
<h3>状态列表</h3>
<el-table :data="props.row.lightBoardStatusVOList" :border="false">
<el-table-column label="状态ID" prop="id" />
<el-table-column label="节点ID" prop="nodeId">
<template #default="scope">
<el-tag v-if="scope.row.nodeId" type="primary">{{
scope.row.nodeId
}}</el-tag>
<el-tag v-else type="info">无节点ID</el-tag>
</template>
</el-table-column>
<el-table-column label="状态名称" prop="lightBoardStart" />
<el-table-column label="状态权重" prop="weightNum" />
<el-table-column label="当前信号" prop="currentSignal" >
<template #default="item">
<el-tag v-if="item.row.currentSignal==false" type="success"></el-tag>
<el-tag v-else type="danger">出现信号</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column label="光字牌ID" prop="id" />
<el-table-column label="光字牌名称" prop="lightBoardName" />
<el-table-column label="绑定状态数量" prop="lightBoardStartNum" />
<el-table-column
prop="lightBoardTags"
label="绑定状态名称-tag"
width="240"
:tooltip-formatter="tableRowTooltipFormatter"
>
<template #default="{ row }">
<el-tag
v-for="tag in row.lightBoardTags"
:key="tag"
class="tag-item"
type="primary"
>
{{ tag }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="权重样式" prop="currentStatarTag" />
</el-table>
</div>
<div style="margin-top: 10px">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 15, 20]"
:pager-count="3"
size="default"
layout="total,sizes,-> ,prev, pager, next"
:total="pageTotal"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
</el-col>
</el-row>
</div>
<NodeListDialog v-model="dialogTableVisible" />
</template>
<script setup lang="ts">
import { ref, reactive ,onMounted} from 'vue';
import { ElMessage, ElLink, type TableTooltipData } from 'element-plus';
import { modelApi } from '@/utils/request';
import NodeListDialog from '@/components/public-compoents/node-list-dialog.vue';
const dialogTableVisible = ref(false);
let currentPage = ref(1); //
let pageSize = ref(10); //
let pageTotal = ref(0); //
const handleSizeChange = (val: number) => {
console.log('size点击:', `${val} items per page`);
getLightBoardList();
};
const handleCurrentChange = (val: number) => {
console.log('Current点击:', `current page: ${val}`);
getLightBoardList();
};
const tableRowFormatter = (data: TableTooltipData<LightBoardList>) => {
return `${data.cellValue}: table formatter`;
};
const tableRowTooltipFormatter = ({ row }: { row: LightBoardList }) => {
return row.lightBoardTags.join(', ');
};
const bindStatus = (row: LightBoardList) => {
console.log('key:', dialogTableVisible.value);
console.log('绑定状态:', row);
dialogTableVisible.value=true
};
//
interface LightBoardList {
id: string;
lightBoardName: string; //
stalightBoardStartNumte: string; //
lightBoardTags: string[]; //
currentStatarTag: string; //
lightBoardStatusVOList: LightBoardStatusVO[]; //
}
interface LightBoardStatusVO {
id: string; //ID
lightBoardId: string; //ID
nodeId: string; //ID
lightBoardStart: string; //
weightNum: string; //
currentSignal: string; //
}
const preserveExpanded = ref(false);
const formInline = reactive({
lightBoardId: '', //ID
lightBoardName: '' //
});
const lightBoardList = ref<LightBoardList[]>([]);
async function getLightBoardList() {
let endJson = {
pageNum: currentPage.value,
pageSize: pageSize.value
};
const response = await modelApi.getLightBoardList_post(endJson);
if (response.code != 200 || response.data == null) {
ElMessage.error(response.message);
} else {
lightBoardList.value.length = 0;
lightBoardList.value = response.data.list;
pageTotal.value = response.data.total;
// pageSize.value = response.data.pageSize;
currentPage.value = response.data.current;
ElMessage.success('接口请求成功');
}
}
onMounted(() => {
getLightBoardList();
});
</script>
<style lang="less" scoped>
.layout-container {
width: 99%;
height: 94vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.form-inline {
width: 100%;
height: 45px;
display: flex;
align-items: center; /* 垂直居中 el-form-item */
}
/* 强制 el-form-item 垂直居中 */
:deep(.el-form-item) {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
:deep(.el-card__body) {
padding: 5px;
}
.tag-item + .tag-item {
margin-left: 5px;
}
.card-form {
// margin-right: 1vh;
padding: 0;
}
.card-table {
height: calc(100% - 60px);
overflow: hidden;
}
/* 第二个卡片容器占满剩余空间 */
.el-row:nth-child(2) {
flex: 1;
height: 0;
}
/* 第二个卡片高度 100% */
.el-row:nth-child(2) .el-card {
height: 100%;
}
</style>
Loading…
Cancel
Save