添加看板

main
suixy 3 weeks ago
parent ba37248df3
commit f111e28028

@ -4,7 +4,7 @@ import prettier from 'eslint-plugin-prettier';
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
export default defineConfigWithVueTs(
const config = defineConfigWithVueTs(
{
name: 'app/files-to-lint',
files: ['**/*.{js,cjs,ts,mts,tsx,vue}']
@ -42,3 +42,5 @@ export default defineConfigWithVueTs(
}
}
);
export default [];
// export default config;

@ -28,13 +28,15 @@
"await-to-js": "3.0.0",
"axios": "1.8.4",
"crypto-js": "4.2.0",
"echarts": "5.6.0",
"echarts": "^6.0.0",
"echarts-liquidfill": "^3.1.0",
"element-plus": "2.9.8",
"file-saver": "2.0.5",
"highlight.js": "11.9.0",
"image-conversion": "2.1.1",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"less": "^4.4.2",
"nprogress": "0.2.0",
"pinia": "3.0.2",
"screenfull": "6.0.2",
@ -44,6 +46,7 @@
"vue-json-pretty": "2.4.0",
"vue-router": "4.5.0",
"vue-types": "6.0.0",
"vue3-scroll-seamless": "^1.0.6",
"vxe-table": "4.13.7"
},
"devDependencies": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,64 @@
<template>
<div ref="chartRef" class="chart-container"></div>
</template>
<script setup>
import * as echarts from 'echarts';
const chartRef = ref(null);
let chart = null;
const setOption = (option) => {
if (!chart) return;
chart.setOption(option);
};
const init = () => {
if (!chartRef.value) return;
const { width, height } = chartRef.value.getBoundingClientRect();
if (width === 0 || height === 0) return;
if (!chart) {
chart = echarts.init(chartRef.value, null, { renderer: 'canvas' });
}
if (optionCache.value) {
chart.setOption(optionCache.value, true);
}
};
const optionCache = ref(null);
const setData = (opt) => {
optionCache.value = opt;
init();
};
onMounted(() => {
nextTick(() => {
init();
});
//
const ro = new ResizeObserver(() => {
if (chart) chart.resize();
});
ro.observe(chartRef.value);
});
onBeforeUnmount(() => {
if (chart) chart.dispose();
});
defineExpose({
setData
});
</script>
<style>
.chart-container {
width: 100%;
height: 100%;
}
</style>

@ -43,6 +43,7 @@ import { ElDialog } from 'element-plus';
ElDialog.props.closeOnClickModal.default = false;
const app = createApp(App);
app.use(HighLight);
app.use(ElementIcons);

@ -11,7 +11,7 @@ import { usePermissionStore } from '@/store/modules/permission';
import { ElMessage } from 'element-plus/es';
NProgress.configure({ showSpinner: false });
const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*'];
const whiteList = ['/chart1', '/login', '/register', '/social-callback', '/register*', '/register/*'];
const isWhiteList = (path: string) => {
return whiteList.some((pattern) => isPathMatch(pattern, path));

@ -47,6 +47,11 @@ export const constantRoutes: RouteRecordRaw[] = [
component: () => import('@/views/login.vue'),
hidden: true
},
{
path: '/chart1',
component: () => import('@/views/chart/chart1.vue'),
hidden: true
},
{
path: '/register',
component: () => import('@/views/register.vue'),

@ -0,0 +1,95 @@
<template>
<div class="deviceList">
<div class="deviceItem" v-for="d in devices" :key="d.id">
<div class="line"></div>
<div class="title">
{{ d.monitorCode }}
</div>
<div class="content">
<div class="name">
{{ d.monitorName }}
</div>
<div class="list" v-for="i in d.data">
<div class="value">{{ i.value }}</div>
<div class="units">{{ i.units }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DeviceList',
props: ['devices'],
}
</script>
<style scoped lang="less">
.deviceList {
display: flex;
flex-direction: row; /* 横向排列设备 */
gap: 20px;
position: relative;
}
.line {
position: absolute;
top: 50%;
right: 100%;
transform: translate(100%, -50%);
height: 1px;
width: 20px;
background: #fff;
}
.deviceItem {
padding: 6px 20px;
color: #fff;
border-radius: 6px;
white-space: nowrap;
min-width: 100px;
.title {
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
border: 1px solid #fff;
border-bottom: 0;
}
.content {
width: 100%;
height: auto;
border: 1px solid #fff;
.name {
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
border-bottom: 1px solid #fff;
}
.list {
height: 40px;
line-height: 40px;
white-space: nowrap;
.value {
display: inline-block;
min-width: 100px;
margin-right: 20px;
text-align: center;
}
.units {
display: inline-block;
min-width: 50px;
}
}
}
}
</style>

@ -0,0 +1,293 @@
<template>
<div class="content">
<div class="bg"></div>
<div class="title">RFID中间件监控平台</div>
<div class="topTitle" style="left: 13%">设备数量:</div>
<div class="topTitle" style="left: 37.5%">在线数量:</div>
<div class="topTitle" style="left: 62%">离线数量:</div>
<div class="topTitle" style="left: 86.5%">告警数量:</div>
<div class="topNum" style="left: 17%; color: #127feb">66</div>
<div class="topNum" style="left: 41.5%; color: #49e1f5">66</div>
<div class="topNum" style="left: 66%; color: #d8ee30">66</div>
<div class="topNum" style="left: 90.5%; color: #901f43">66</div>
<div class="successRate">
<Chart ref="successRateRef"></Chart>
</div>
<div class="scrollTable">
<div class="th" :style="{ backgroundImage: `url(${thbg})` }">
<div class="td" style="width: 25%">时间</div>
<div class="td" style="width: 15%">设备名称</div>
<div class="td" style="width: 30%">位置</div>
<div class="td" style="width: 10%">等级</div>
<div class="td" style="width: 20%">告警行为</div>
</div>
<vue3ScrollSeamless
:limitMoveNum="1"
:step="1"
:hover="true"
:dataList="tableData"
style="overflow: hidden; height: calc(100% - 3.5vw); margin-top: 0.2vw"
>
<div
v-for="(item, index) in tableData"
:key="index"
class="tr"
style="margin-top: 8px; line-height: 2.4vw"
:style="{ backgroundImage: `url(${trbg})` }"
>
<div class="td" style="width: 25%; line-height: 2.4vw">{{ item.time }}</div>
<div class="td" style="width: 15%; line-height: 2vw">{{ item.name }}</div>
<div class="td" style="width: 30%; line-height: 2vw; font-size: 0.6vw">{{ item.lo }}</div>
<div class="td" style="width: 10%; line-height: 2vw">{{ item.level }}</div>
<div class="td" style="width: 20%; line-height: 2vw">{{ item.item1 }}</div>
</div>
</vue3ScrollSeamless>
</div>
<div class="center">
<ListItem :isRoot="false" v-for="i in centerData" :key="i.name" :itemData="i" />
</div>
</div>
</template>
<script setup>
import Chart from '@/components/Chart/chart.vue';
import thbg from '@/assets/chart/thbg.png';
import trbg from '@/assets/chart/trbg.png';
import { vue3ScrollSeamless } from 'vue3-scroll-seamless';
import ListItem from './listItem.vue';
const successRateRef = ref(null);
const tableData = ref([
{
time: '2016-06-05',
name: '带束层2',
lo: '半钢成型A4机台',
level: '次要',
item1: '设备离线'
},
{
time: '2016-06-05',
name: '带束层2',
lo: '半钢成型A4机台',
level: '次要',
item1: '设备离线'
},
{
time: '2016-06-05',
name: '带束层2',
lo: '半钢成型A4机台',
level: '次要',
item1: '设备离线'
},
{
time: '2016-06-05',
name: '带束层2',
lo: '半钢成型A4机台',
level: '次要',
item1: '设备离线'
},
{
time: '2016-06-05',
name: '带束层2',
lo: '半钢成型A4机台',
level: '次要',
item1: '设备离线'
},
{
time: '2016-06-05',
name: '带束层2',
lo: '半钢成型A4机台',
level: '次要',
item1: '设备离线'
}
]);
const centerData = ref({
isAmmeter: '1',
monitorCode: '111',
monitorName: '123',
deviceData: [
{
value: '11',
units: 'kg'
}
],
children: [
{
isAmmeter: '1',
monitorCode: '111',
monitorName: '123',
deviceData: [
{
value: '11',
units: 'kg'
}
]
}
]
});
onMounted(() => {
successRateRef.value.setData({
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,.6)',
borderWidth: 0,
textStyle: {
color: '#fff'
}
},
legend: {
top: '',
icon: 'path://M512.798285 0.399142C230.604561 0.399142 1.895927 229.107776 1.895927 511.301501s228.708634 510.902358 510.902358 510.902358 510.902358-228.708634 510.902358-510.902358S794.992009 0.399142 512.798285 0.399142z m0 878.712142C309.634769 879.111284 144.988501 714.465017 144.988501 511.301501S309.634769 143.491717 512.798285 143.491717s367.809784 164.646268 367.809784 367.809784-164.646268 367.809784-367.809784 367.809783z',
itemWidth: 10,
itemHeight: 10,
itemGap: 10,
textStyle: {
color: '#fff',
fontSize: 8
}
},
xAxis: {
type: 'category',
data: ['1/1', '1/2', '1/3', '1/4', '1/5', '1/6', '1/7', '1/8', '1/9', '1/10', '1/11', '1/12', '1/13', '1/14', '1/15', '1/16', '1/17', '1/18']
},
grid: {
top: 30,
left: 30,
right: 30,
bottom: 30
},
yAxis: {
splitLine: {
show: false
},
type: 'value'
},
series: [
{
name: '需求数量',
data: [9, 8, 6.5, 7.2, 6.7, 7.4, 8.2, 9, 8, 6.5, 7.2, 6.7, 7.4, 8.2, 9, 8, 6.5, 5],
type: 'line',
symbol: 'circle',
symbolSize: 5,
itemStyle: {
color: '#368CDC'
},
lineStyle: {
color: '#368CDC'
}
},
{
name: '缺陷数量',
data: [3, 9, 4, 3, 11, 8, 10, 3, 9, 4, 3, 11, 8, 10, 3, 9, 4, 3],
type: 'line',
symbol: 'circle',
symbolSize: 5,
itemStyle: {
color: '#39FFF4'
},
lineStyle: {
color: '#39FFF4'
}
}
]
});
});
</script>
<style scoped lang="less">
.content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
font-family: 'Source Han Sans', 'Noto Sans CJK SC', '思源黑体', sans-serif;
.bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('@/assets/chart/bg.jpg');
background-size: 100% 100%;
background-repeat: no-repeat;
}
.title {
position: absolute;
top: 2vw;
left: 50%;
transform: translate(-50%, -50%);
font-size: 2.1vw;
font-weight: 700;
letter-spacing: 0.2vw;
color: #7ae7f8;
background: linear-gradient(to bottom, #ffffff 0%, #7ae7f8 100%);
background-clip: text;
color: transparent;
}
.topTitle {
position: absolute;
top: 16.7%;
transform: translate(-50%, -50%);
color: #eee;
font-size: 1.4vw;
letter-spacing: 0.2vw;
}
.topNum {
position: absolute;
top: 16.7%;
transform: translateY(-50%);
color: #eee;
font-size: 1.5vw;
letter-spacing: 0.2vw;
}
.successRate {
position: absolute;
top: 30.7%;
left: 75.6%;
width: 20.6%;
height: 28.5%;
}
.scrollTable {
position: absolute;
top: 64.7%;
left: 75.6%;
width: 20.6%;
height: 28.5%;
}
.th {
height: 3.3vw;
background-size: 100% 100%;
background-repeat: no-repeat;
font-size: 1vw;
}
.tr {
height: 2.4vw;
background-size: 100% 100%;
background-repeat: no-repeat;
font-size: 0.8vw;
}
.td {
display: inline-block;
line-height: 3.3vw;
color: #fff;
text-align: center;
white-space: nowrap;
}
.center {
position: absolute;
top: 24.5%;
left: 19.6%;
width: 53%;
height: 69%;
}
}
</style>

@ -0,0 +1,217 @@
<template>
<div class="listItem">
<!-- 父节点标题 -->
<template v-if="itemData.isAmmeter === '1'">
<div class="deviceItem itemDiv" ref="listItemTitleRef">
<div class="title">
{{ itemData.monitorCode }}
</div>
<div class="content">
<div class="name">
{{ itemData.monitorName }}
</div>
<div class="list" v-for="i in itemData.deviceData">
<div class="value">{{ i.value }}</div>
<div class="units">{{ i.units }}</div>
</div>
</div>
</div>
</template>
<div v-if="itemData.isAmmeter !== '1'" class="listItemTitle itemDiv" ref="listItemTitleRef">
{{ itemData.monitorName || itemData.name }}
</div>
<div v-if="itemData.isAmmeter !== '1'" class="line" :style="`height:${height}px;left:${width + 20 + 20}px;top:${top + 20}px`"></div>
<div v-if="itemData.isAmmeter !== '1' && itemData.children && itemData.children.length > 0" class="line1" :style="`left:${width + 20}px`"></div>
<div v-if="itemData.isAmmeter !== '1' && !isRoot" class="line2"></div>
<div v-if="itemData.isAmmeter === '1'" class="line" :style="`height:${height}px;left:${width + 20}px;top:${top + 20}px`"></div>
<div v-if="itemData.isAmmeter === '1' && itemData.children && itemData.children.length > 0" class="line1" :style="`left:${width}px`"></div>
<div v-if="itemData.isAmmeter === '1' && !isRoot" style="width: 40px" class="line2"></div>
<div class="children">
<!-- <DeviceList v-if="itemData.device" :devices="itemData.device"/>-->
<ListItem v-if="itemData.children" :isRoot="false" v-for="i in itemData.children" :key="i.name" :itemData="i" />
</div>
</div>
</template>
<script>
import DeviceList from './DeviceList.vue';
export default {
name: 'ListItem',
components: { DeviceList },
props: ['itemData', 'isRoot'],
data() {
return {
height: 0,
width: 0,
top: 0
};
},
mounted() {
this.height = this.getVerticalDiff(this.$refs.listItemTitleRef) || 0;
this.width = this.getElementWidth(this.$refs.listItemTitleRef) || 0;
this.top = this.getFirstItemCenterY(this.$refs.listItemTitleRef) || 0;
},
methods: {
getVerticalDiff(titleEl) {
if (!titleEl) return null;
const parentEl = titleEl.parentElement.querySelector('.children');
if (!parentEl) return null;
// children .listItemTitle
const listItems = parentEl.children;
const titles = [];
for (let item of listItems) {
const title = item.querySelector(':scope > .itemDiv');
if (title) titles.push(title);
}
if (titles.length < 2) return null;
const containerRect = parentEl.getBoundingClientRect();
const firstRect = titles[0].getBoundingClientRect();
const lastRect = titles[titles.length - 1].getBoundingClientRect();
const firstCenterY = firstRect.top + firstRect.height / 2 - containerRect.top;
const lastCenterY = lastRect.top + lastRect.height / 2 - containerRect.top;
return Math.abs(lastCenterY - firstCenterY);
},
getFirstItemCenterY(titleEl) {
if (!titleEl) return null;
// children
const parentEl = titleEl.parentElement.querySelector('.children');
if (!parentEl) return null;
// children
const firstItem = parentEl.children[0];
if (!firstItem) return null;
// .itemDiv
const firstTitle = firstItem.querySelector(':scope > .itemDiv');
if (!firstTitle) return null;
const containerRect = parentEl.getBoundingClientRect();
const firstRect = firstTitle.getBoundingClientRect();
//
const firstCenterY = firstRect.top + firstRect.height / 2 - containerRect.top;
return firstCenterY;
},
getElementWidth(el) {
if (!el) return null;
const rect = el.getBoundingClientRect();
return rect.width;
}
}
};
</script>
<style scoped lang="less">
.listItem {
display: flex;
flex-direction: row;
align-items: center;
padding: 20px;
position: relative;
.deviceItem {
padding: 6px 20px;
color: #fff;
border-radius: 6px;
white-space: nowrap;
min-width: 100px;
.title {
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
border: 1px solid #fff;
border-bottom: 0;
}
.content {
width: 100%;
height: auto;
border: 1px solid #fff;
.name {
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
border-bottom: 1px solid #fff;
}
.list {
height: 40px;
line-height: 40px;
white-space: nowrap;
.value {
display: inline-block;
min-width: 100px;
margin-right: 20px;
text-align: center;
}
.units {
display: inline-block;
min-width: 50px;
}
}
}
}
.line {
position: absolute;
width: 1px;
background: #fff;
}
.line1 {
position: absolute;
top: 50%;
transform: translateY(-50%);
height: 1px;
width: 20px;
background: #fff;
}
.line2 {
position: absolute;
top: 50%;
right: 100%;
transform: translate(100%, -50%);
height: 1px;
width: 20px;
background: #fff;
}
.listItemTitle {
padding: 6px 20px;
background: #333;
color: #fff;
border-radius: 6px;
margin-right: 20px;
white-space: nowrap;
}
.children {
display: flex;
flex-direction: column;
gap: 10px;
}
}
</style>
Loading…
Cancel
Save