Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
b02f7f5add
|
@ -0,0 +1,19 @@
|
||||||
|
<template>
|
||||||
|
<div style="width: 100%; height: 400px">
|
||||||
|
<el-amap
|
||||||
|
>
|
||||||
|
</el-amap>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { initAMapApiLoader } from '@vuemap/vue-amap';
|
||||||
|
import '@vuemap/vue-amap/dist/style.css';
|
||||||
|
initAMapApiLoader({
|
||||||
|
// key: '95fa72137f4263f8e64ae01f766ad09c',
|
||||||
|
key: 'a0415acfc35af15f10221bfa5a6850b4',
|
||||||
|
securityJsCode: 'cae6108ec3dd222f946d1a7237c78be0',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
|
@ -0,0 +1,58 @@
|
||||||
|
<template>
|
||||||
|
<div class="home-title">
|
||||||
|
<div v-if="title">{{ title }}</div>
|
||||||
|
<div v-else>
|
||||||
|
<slot name="title"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="extra-text">
|
||||||
|
<slot name="extra"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="home-title-english">{{ english }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="Guide">
|
||||||
|
interface guideProps {
|
||||||
|
title?: string;
|
||||||
|
english?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<guideProps>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.home-title {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-left: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: @primary-color;
|
||||||
|
border: 1px solid #b4c0da;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-text {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-title-english {
|
||||||
|
position: absolute;
|
||||||
|
top: 30px;
|
||||||
|
color: rgba(0, 0, 0, 0.3);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,112 @@
|
||||||
|
<template>
|
||||||
|
<div class="chart" ref="chart"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
const { proxy } = <any>getCurrentInstance();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 图表数据
|
||||||
|
x: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
maxY:{
|
||||||
|
type:Number,
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制图表
|
||||||
|
*/
|
||||||
|
const createChart = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
const myChart = echarts.init(proxy.$refs.chart);
|
||||||
|
const options = {
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: props.x,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: '{b0}<br />{a0}: {c0}',
|
||||||
|
// formatter: '{b0}<br />{a0}: {c0}<br />{a1}: {c1}%'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: '2%',
|
||||||
|
bottom: '5%',
|
||||||
|
left: props.maxY > 100000 ? '90px' : '50px',
|
||||||
|
right: '50px',
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '消息量',
|
||||||
|
data: props.y,
|
||||||
|
type: 'bar',
|
||||||
|
// type: 'line',
|
||||||
|
// smooth: true,
|
||||||
|
color: '#597EF7',
|
||||||
|
barWidth: '30%',
|
||||||
|
// areaStyle: {
|
||||||
|
// color: {
|
||||||
|
// type: 'linear',
|
||||||
|
// x: 0,
|
||||||
|
// y: 0,
|
||||||
|
// x2: 0,
|
||||||
|
// y2: 1,
|
||||||
|
// colorStops: [
|
||||||
|
// {
|
||||||
|
// offset: 0,
|
||||||
|
// color: '#685DEB', // 100% 处的颜色
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// offset: 1,
|
||||||
|
// color: '#FFFFFF', // 0% 处的颜色
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// global: false, // 缺省为 false
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '占比',
|
||||||
|
data: props.y,
|
||||||
|
// data: percentageY,
|
||||||
|
type: 'line',
|
||||||
|
smooth: true,
|
||||||
|
symbolSize: 0, // 拐点大小
|
||||||
|
color: '#96ECE3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
myChart.setOption(options);
|
||||||
|
window.addEventListener('resize', function () {
|
||||||
|
myChart.resize();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.y,
|
||||||
|
() => createChart(),
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,117 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-radio-group
|
||||||
|
v-if="quickBtn"
|
||||||
|
default-value="today"
|
||||||
|
button-style="solid"
|
||||||
|
v-model:value="radioValue"
|
||||||
|
@change="(e) => handleBtnChange(e.target.value)"
|
||||||
|
>
|
||||||
|
<a-radio-button
|
||||||
|
v-for="item in quickBtnList"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-range-picker
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
valueFormat="YYYY-MM-DD HH:mm:ss"
|
||||||
|
style="margin-left: 12px"
|
||||||
|
@change="rangeChange"
|
||||||
|
v-model:value="rangeVal"
|
||||||
|
:allowClear="false"
|
||||||
|
>
|
||||||
|
</a-range-picker>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import moment from 'moment';
|
||||||
|
import { PropType } from 'vue';
|
||||||
|
|
||||||
|
interface BtnOptions {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EmitProps {
|
||||||
|
(e: 'change', data: Record<string, any>): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<EmitProps>();
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 显示快捷按钮
|
||||||
|
quickBtn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
// 快捷按钮列表
|
||||||
|
quickBtnList: {
|
||||||
|
type: Array as PropType<BtnOptions[]>,
|
||||||
|
default: [
|
||||||
|
{ label: '今日', value: 'today' },
|
||||||
|
{ label: '近一周', value: 'week' },
|
||||||
|
{ label: '近一月', value: 'month' },
|
||||||
|
{ label: '近一年', value: 'year' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'today',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const radioValue = ref(props.type || 'week' || undefined);
|
||||||
|
const rangeVal = ref<[string, string]>();
|
||||||
|
|
||||||
|
const rangeChange = (val: any) => {
|
||||||
|
radioValue.value = undefined;
|
||||||
|
emit('change', {
|
||||||
|
start: moment(val[0]).valueOf(),
|
||||||
|
end: moment(val[1]).valueOf(),
|
||||||
|
type: undefined,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTimeByType = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'hour':
|
||||||
|
return moment().subtract(1, 'hours').valueOf();
|
||||||
|
case 'week':
|
||||||
|
return moment().subtract(6, 'days').valueOf();
|
||||||
|
case 'month':
|
||||||
|
return moment().subtract(29, 'days').valueOf();
|
||||||
|
case 'year':
|
||||||
|
return moment().subtract(365, 'days').valueOf();
|
||||||
|
default:
|
||||||
|
return moment().startOf('day').valueOf();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBtnChange = (val: string) => {
|
||||||
|
radioValue.value = val;
|
||||||
|
let endTime = moment(new Date()).valueOf();
|
||||||
|
let startTime = getTimeByType(val);
|
||||||
|
if (val === 'yesterday') {
|
||||||
|
startTime = moment().subtract(1, 'days').startOf('day').valueOf();
|
||||||
|
endTime = moment().subtract(1, 'days').endOf('day').valueOf();
|
||||||
|
}
|
||||||
|
rangeVal.value = [
|
||||||
|
moment(startTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
moment(endTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
];
|
||||||
|
emit('change', {
|
||||||
|
start: startTime,
|
||||||
|
end: endTime,
|
||||||
|
type: val,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
handleBtnChange(radioValue.value);
|
||||||
|
watch(
|
||||||
|
() => radioValue.value,
|
||||||
|
{ deep: true, immediate: true },
|
||||||
|
);
|
||||||
|
</script>
|
|
@ -24,7 +24,10 @@
|
||||||
:footer="onlineFooter"
|
:footer="onlineFooter"
|
||||||
:value="onlineToday"
|
:value="onlineToday"
|
||||||
>
|
>
|
||||||
<BarChart :chartXData="barChartXData" :chartYData="barChartYData"></BarChart> </TopCard
|
<BarChart
|
||||||
|
:chartXData="barChartXData"
|
||||||
|
:chartYData="barChartYData"
|
||||||
|
></BarChart> </TopCard
|
||||||
></a-col>
|
></a-col>
|
||||||
<a-col :span="6"
|
<a-col :span="6"
|
||||||
><TopCard
|
><TopCard
|
||||||
|
@ -32,15 +35,50 @@
|
||||||
:footer="messageFooter"
|
:footer="messageFooter"
|
||||||
:value="dayMessage"
|
:value="dayMessage"
|
||||||
>
|
>
|
||||||
<LineChart :chartXData="lineChartXData" :chartYData="lineChartYData"></LineChart> </TopCard
|
<LineChart
|
||||||
|
:chartXData="lineChartXData"
|
||||||
|
:chartYData="lineChartYData"
|
||||||
|
></LineChart> </TopCard
|
||||||
></a-col>
|
></a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
<a-row :span="24">
|
||||||
|
<a-col :span="24">
|
||||||
|
<div class="message-card">
|
||||||
|
<Guide title="设备消息">
|
||||||
|
<template #extra>
|
||||||
|
<TimeSelect
|
||||||
|
key="flow-static"
|
||||||
|
:type="'week'"
|
||||||
|
:quickBtnList="quickBtnList"
|
||||||
|
@change="getEcharts"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Guide>
|
||||||
|
<div class="message-chart">
|
||||||
|
<MessageChart :x="messageChartXData" :y="messageChartYData" :maxY="messageMaxChartYData"></MessageChart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row :span="24">
|
||||||
|
<a-col :span="24">
|
||||||
|
<div class="device-position">
|
||||||
|
<Guide title="设备分布"></Guide>
|
||||||
|
<div class="device-map">
|
||||||
|
<Amap></Amap>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
</page-container>
|
</page-container>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import BarChart from './components/BarChart.vue';
|
import BarChart from './components/BarChart.vue';
|
||||||
import LineChart from './components/LineChart.vue';
|
import LineChart from './components/LineChart.vue';
|
||||||
|
import TimeSelect from './components/TimeSelect.vue';
|
||||||
|
import Guide from './components/Guide.vue';
|
||||||
|
import MessageChart from './components/messageChart.vue';
|
||||||
import {
|
import {
|
||||||
productCount,
|
productCount,
|
||||||
deviceCount,
|
deviceCount,
|
||||||
|
@ -51,6 +89,7 @@ import encodeQuery from '@/utils/encodeQuery';
|
||||||
import { getImage } from '@/utils/comm';
|
import { getImage } from '@/utils/comm';
|
||||||
import type { Footer } from '@/views/device/DashBoard/typings';
|
import type { Footer } from '@/views/device/DashBoard/typings';
|
||||||
import TopCard from '@/views/device/DashBoard/components/TopCard.vue';
|
import TopCard from '@/views/device/DashBoard/components/TopCard.vue';
|
||||||
|
import Amap from './components/Amap.vue'
|
||||||
let productTotal = ref(0);
|
let productTotal = ref(0);
|
||||||
let productFooter = ref<Footer[]>([
|
let productFooter = ref<Footer[]>([
|
||||||
{
|
{
|
||||||
|
@ -95,6 +134,15 @@ let lineChartYData = ref<any[]>([]);
|
||||||
let lineChartXData = ref<any[]>([]);
|
let lineChartXData = ref<any[]>([]);
|
||||||
let barChartXData = ref<any[]>([]);
|
let barChartXData = ref<any[]>([]);
|
||||||
let barChartYData = ref<any[]>([]);
|
let barChartYData = ref<any[]>([]);
|
||||||
|
let messageChartXData = ref<any[]>([]);
|
||||||
|
let messageChartYData = ref<any[]>([]);
|
||||||
|
let messageMaxChartYData = ref<number>();
|
||||||
|
const quickBtnList = [
|
||||||
|
{ label: '昨日', value: 'yesterday' },
|
||||||
|
{ label: '近一周', value: 'week' },
|
||||||
|
{ label: '近一月', value: 'month' },
|
||||||
|
{ label: '近一年', value: 'year' },
|
||||||
|
];
|
||||||
const getProductData = () => {
|
const getProductData = () => {
|
||||||
productCount().then((res) => {
|
productCount().then((res) => {
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
|
@ -224,19 +272,81 @@ const getDevice = () => {
|
||||||
const oneDay = res.result.find(
|
const oneDay = res.result.find(
|
||||||
(item: any) => item.group === 'oneday',
|
(item: any) => item.group === 'oneday',
|
||||||
)?.data.value;
|
)?.data.value;
|
||||||
dayMessage = oneDay;
|
dayMessage.value = oneDay;
|
||||||
messageFooter.value[0].value = thisMonth;
|
messageFooter.value[0].value = thisMonth;
|
||||||
const today = res.result.filter(
|
const today = res.result.filter(
|
||||||
(item: any) => item.group === 'today',
|
(item: any) => item.group === 'today',
|
||||||
);
|
);
|
||||||
const x = today.map((item: any) => item.data.timeString).reverse();
|
const x = today.map((item: any) => item.data.timeString).reverse();
|
||||||
const y = today.map((item: any) => item.data.value).reverse();
|
const y = today.map((item: any) => item.data.value).reverse();
|
||||||
lineChartXData.value = x ;
|
lineChartXData.value = x;
|
||||||
lineChartYData.value = y ;
|
lineChartYData.value = y;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
getDevice();
|
getDevice();
|
||||||
|
const getEcharts = (data: any) => {
|
||||||
|
let _time = '1h';
|
||||||
|
let format = 'HH';
|
||||||
|
let limit = 12;
|
||||||
|
const dt = data.end - data.start;
|
||||||
|
const hour = 60 * 60 * 1000;
|
||||||
|
const days = hour * 24;
|
||||||
|
const months = days * 30;
|
||||||
|
const year = 365 * days;
|
||||||
|
if (dt <= days) {
|
||||||
|
limit = Math.abs(Math.ceil(dt / hour));
|
||||||
|
} else if (dt > days && dt < year) {
|
||||||
|
limit = Math.abs(Math.ceil(dt / days)) + 1;
|
||||||
|
_time = '1d';
|
||||||
|
format = 'M月dd日';
|
||||||
|
} else if (dt >= year) {
|
||||||
|
limit = Math.abs(Math.floor(dt / months));
|
||||||
|
_time = '1M';
|
||||||
|
format = 'yyyy年-M月';
|
||||||
|
}
|
||||||
|
dashboard([
|
||||||
|
{
|
||||||
|
dashboard: 'device',
|
||||||
|
object: 'message',
|
||||||
|
measurement: 'quantity',
|
||||||
|
dimension: 'agg',
|
||||||
|
group: 'device_msg',
|
||||||
|
params: {
|
||||||
|
time: _time,
|
||||||
|
format: format,
|
||||||
|
limit: limit,
|
||||||
|
from: data.start,
|
||||||
|
to: data.end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]).then((res:any) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
messageChartXData.value = res.result
|
||||||
|
.map((item: any) =>
|
||||||
|
_time === '1h'
|
||||||
|
? `${item.data.timeString}时`
|
||||||
|
: item.data.timeString,
|
||||||
|
)
|
||||||
|
.reverse();
|
||||||
|
messageChartYData.value = res.result.map((item: any) => item.data.value).reverse();
|
||||||
|
messageMaxChartYData.value = Math.max.apply(null, messageChartYData.value.length ? messageChartYData.value : [0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
.message-card,.device-position{
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 24px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
.message-chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
.amap-box{
|
||||||
|
height: 500px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
Reference in New Issue