Merge branch 'dev' of github.com:jetlinks/jetlinks-ui-vue into dev
This commit is contained in:
commit
7daa9df6a6
|
@ -0,0 +1,13 @@
|
|||
import server from '@/utils/request'
|
||||
|
||||
export default {
|
||||
// 列表
|
||||
list: (data: any) => server.post(`/media/device/_query/`, data),
|
||||
// 详情
|
||||
detail: (id: string): any => server.get(`/media/device/${id}`),
|
||||
// 新增
|
||||
save: (data: any) => server.post(`/media/device/${data.channel}`, data),
|
||||
// 修改
|
||||
update: (data: any) => server.put(`/media/device/${data.channel}/${data.id}`, data),
|
||||
del: (id: string) => server.remove(`/media/device/${id}`),
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import server from '@/utils/request';
|
||||
/**
|
||||
* 获取今日及当月告警数量
|
||||
*/
|
||||
export const dashboard = (data:Record<string,any[]>)=> server.post('/dashboard/_multi',data);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const getAlarm = (params:Record<string,any[]>) => server.get('/alarm/record/_query',params);
|
||||
|
||||
/**
|
||||
* 获取告警数量
|
||||
*/
|
||||
export const getAlarmConfigCount = (data:Record<string,any>) => server.post('/alarm/config/_count',data);
|
||||
|
||||
/**
|
||||
* 获取报警等级
|
||||
*/
|
||||
export const getAlarmLevel = () => server.get('/alarm/config/default/level');
|
|
@ -45,7 +45,10 @@ const iconKeys = [
|
|||
'InfoCircleOutlined',
|
||||
'SearchOutlined',
|
||||
'EllipsisOutlined',
|
||||
'ClockCircleOutlined'
|
||||
'ClockCircleOutlined',
|
||||
'PartitionOutlined',
|
||||
'ShareAltOutlined',
|
||||
'playCircleOutlined',
|
||||
]
|
||||
|
||||
const Icon = (props: {type: string}) => {
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<Charts :options="TodayDevOptions"></Charts> </TopCard
|
||||
></a-col>
|
||||
</a-row>
|
||||
<a-row :span="24">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<div class="message-card">
|
||||
<Guide title="设备消息">
|
||||
|
@ -452,6 +452,7 @@ const getEcharts = (data: any) => {
|
|||
_time = '1M';
|
||||
format = 'yyyy年-M月';
|
||||
}
|
||||
|
||||
dashboard([
|
||||
{
|
||||
dashboard: 'device',
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<div class="page-container">save</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,334 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<Search
|
||||
:columns="columns"
|
||||
target="notice-config"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<JTable
|
||||
ref="listRef"
|
||||
:columns="columns"
|
||||
:request="DeviceApi.list"
|
||||
:defaultParams="{
|
||||
sorts: [{ name: 'createTime', order: 'desc' }],
|
||||
}"
|
||||
:params="params"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-button type="primary" @click="handleAdd"> 新增 </a-button>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<CardBox
|
||||
:value="slotProps"
|
||||
:actions="getActions(slotProps, 'card')"
|
||||
v-bind="slotProps"
|
||||
:showStatus="true"
|
||||
:status="
|
||||
slotProps.state.value === 'online' ? 'success' : 'error'
|
||||
"
|
||||
:statusText="slotProps.state.text"
|
||||
:statusNames="{ success: 'success', error: 'error' }"
|
||||
>
|
||||
<template #img>
|
||||
<slot name="img">
|
||||
<img :src="getImage('/device-media.png')" />
|
||||
</slot>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3 class="card-item-content-title">
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">厂商</div>
|
||||
<div>{{ slotProps.manufacturer }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
通道数量
|
||||
</div>
|
||||
<div>{{ slotProps.channelNumber }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">型号</div>
|
||||
<div>{{ slotProps.model }}</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
接入方式
|
||||
</div>
|
||||
<div>
|
||||
{{ providerType[slotProps.provider] }}
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<template #actions="item">
|
||||
<a-tooltip
|
||||
v-bind="item.tooltip"
|
||||
:title="item.disabled && item.tooltip.title"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="item.popConfirm"
|
||||
v-bind="item.popConfirm"
|
||||
:disabled="item.disabled"
|
||||
>
|
||||
<a-button :disabled="item.disabled">
|
||||
<AIcon type="DeleteOutlined" />
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<template v-else>
|
||||
<a-button
|
||||
:disabled="item.disabled"
|
||||
@click="item.onClick"
|
||||
>
|
||||
<AIcon :type="item.icon" />
|
||||
<span>{{ item.text }}</span>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps, 'table')"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-popconfirm
|
||||
v-if="i.popConfirm"
|
||||
v-bind="i.popConfirm"
|
||||
:disabled="i.disabled"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-popconfirm>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
v-else
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<a-button
|
||||
:disabled="i.disabled"
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
><AIcon :type="i.icon"
|
||||
/></a-button>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</JTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DeviceApi from '@/api/media/device';
|
||||
import type { ActionsType } from '@/components/Table/index.vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
|
||||
const providerType = {
|
||||
'gb28181-2016': 'GB/T28181',
|
||||
'fixed-media': '固定地址',
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const listRef = ref<Record<string, any>>({});
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '接入方式',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '固定地址', value: 'fixed-media' },
|
||||
{ label: 'GB/T28181', value: 'gb28181-2016' },
|
||||
],
|
||||
handleValue: (v: any) => {
|
||||
return '123';
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '通道数量',
|
||||
dataIndex: 'channelNumber',
|
||||
key: 'channelNumber',
|
||||
},
|
||||
{
|
||||
title: '厂商',
|
||||
dataIndex: 'manufacturer',
|
||||
key: 'manufacturer',
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'productId',
|
||||
key: 'productId',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '固定地址', value: 'fixed-media' },
|
||||
{ label: 'GB/T28181', value: 'gb28181-2016' },
|
||||
],
|
||||
handleValue: (v: any) => {
|
||||
return '123';
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '禁用', value: 'notActive' },
|
||||
{ label: '离线', value: 'offline' },
|
||||
{ label: '在线', value: 'online' },
|
||||
],
|
||||
handleValue: (v: any) => {
|
||||
return '123';
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
width: 250,
|
||||
scopedSlots: true,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* @param params
|
||||
*/
|
||||
const handleSearch = (e: any) => {
|
||||
// console.log('handleSearch:', e);
|
||||
params.value = e;
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*/
|
||||
const handleAdd = () => {
|
||||
router.push(`/media/device/Save`);
|
||||
};
|
||||
|
||||
const getActions = (
|
||||
data: Partial<Record<string, any>>,
|
||||
type: 'card' | 'table',
|
||||
): ActionsType[] => {
|
||||
if (!data) return [];
|
||||
const actions = [
|
||||
{
|
||||
key: 'edit',
|
||||
text: '编辑',
|
||||
tooltip: {
|
||||
title: '编辑',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
router.push(`/media/device/Save?id=${data.id}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'view',
|
||||
text: '查看通道',
|
||||
tooltip: {
|
||||
title: '查看通道',
|
||||
},
|
||||
icon: 'PartitionOutlined',
|
||||
onClick: () => {
|
||||
router.push(
|
||||
`/media/device/Channel?id=${data.id}&type=${data.provider}`,
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'debug',
|
||||
text: '更新通道',
|
||||
tooltip: {
|
||||
title:
|
||||
data.provider === 'fixed-media'
|
||||
? '固定地址无法更新通道'
|
||||
: data.state.value === 'offline'
|
||||
? '设备已离线'
|
||||
: data.state.value === 'notActive'
|
||||
? '设备已禁用'
|
||||
: '',
|
||||
},
|
||||
disabled:
|
||||
data.state.value === 'offline' ||
|
||||
data.state.value === 'notActive' ||
|
||||
data.provider === 'fixed-media',
|
||||
icon: 'SyncOutlined',
|
||||
onClick: () => {
|
||||
// updateChannel()
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
text: '删除',
|
||||
tooltip: {
|
||||
title: '在线设备无法删除',
|
||||
},
|
||||
disabled: data.state.value === 'online',
|
||||
popConfirm: {
|
||||
title: '确认删除?',
|
||||
onConfirm: async () => {
|
||||
const resp = await DeviceApi.del(data.id);
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
listRef.value?.reload();
|
||||
} else {
|
||||
message.error('操作失败!');
|
||||
}
|
||||
},
|
||||
},
|
||||
icon: 'DeleteOutlined',
|
||||
},
|
||||
];
|
||||
return actions;
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.page-container {
|
||||
background: #f0f2f5;
|
||||
padding: 24px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,24 @@
|
|||
type BaseItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
type State = {
|
||||
value: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type DeviceItem = {
|
||||
photoUrl?: string;
|
||||
channelNumber: number;
|
||||
createTime: number;
|
||||
firmware: string;
|
||||
gatewayId: string;
|
||||
host: string;
|
||||
manufacturer: string;
|
||||
model: string;
|
||||
port: number;
|
||||
provider: string;
|
||||
state: State;
|
||||
streamMode: string;
|
||||
transport: string;
|
||||
} & BaseItem;
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<div class="chart" ref="chart"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const { proxy } = <any>getCurrentInstance();
|
||||
|
||||
const props = defineProps({
|
||||
// 图表数据
|
||||
options:{
|
||||
type:Object,
|
||||
default:()=>{}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 绘制图表
|
||||
*/
|
||||
const createChart = () => {
|
||||
nextTick(() => {
|
||||
const myChart = echarts.init(proxy.$refs.chart);
|
||||
myChart.setOption(props.options);
|
||||
window.addEventListener('resize', function () {
|
||||
myChart.resize();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.options,
|
||||
() => createChart(),
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div class="home-title">
|
||||
<div v-if="title">{{ title }}</div>
|
||||
<div v-else class="title">
|
||||
<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;
|
||||
}
|
||||
.title{
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.home-title-english {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<div class="new-alarm">
|
||||
<div class="title">最新警告</div>
|
||||
<div v-if="alarmList.length" class="new-alarm-items">
|
||||
<ul>
|
||||
<li v-for="item in alarmList.slice(0, 3)" :key="item">
|
||||
<div class="new-alarm-item">
|
||||
<div class="new-alarm-item-time">
|
||||
<img
|
||||
:src="getImage('/alarm/bashboard.png')"
|
||||
alt=""
|
||||
/>{{
|
||||
moment(item.alarmTime).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<div class="new-alarm-item-content">
|
||||
<a-tooltip
|
||||
:title="item.alarmName"
|
||||
placement="topLeft"
|
||||
>
|
||||
<a>{{ item.alarmName }}</a>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="new-alarm-item-state">
|
||||
<a-badge
|
||||
:status="
|
||||
item.state?.value === 'warning'
|
||||
? 'error'
|
||||
: 'default'
|
||||
"
|
||||
>
|
||||
</a-badge>
|
||||
<span
|
||||
:class="
|
||||
item.state?.value === 'warning'
|
||||
? 'error'
|
||||
: 'default'
|
||||
"
|
||||
>
|
||||
{{ item.state?.text }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
:class="[
|
||||
'new-alarm-item-level',
|
||||
`level-${item.level}`,
|
||||
]"
|
||||
>
|
||||
{{ item.levelName }}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else class="empty-body">
|
||||
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE"></a-empty>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Empty } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import moment from 'moment';
|
||||
const props = defineProps({
|
||||
alarmList: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.new-alarm {
|
||||
background-color: white;
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #e0e4e8;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.new-alarm-items {
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.new-alarm-item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin: 18px 0;
|
||||
font-size: 12px;
|
||||
.new-alarm-item-time {
|
||||
width: 180px;
|
||||
font-size: 14px;
|
||||
|
||||
> img {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.new-alarm-item-content {
|
||||
width: ~'calc(100% - 360px)';
|
||||
}
|
||||
.new-alarm-item-state {
|
||||
width: 90px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
.error {
|
||||
color: @error-color;
|
||||
}
|
||||
|
||||
.default {
|
||||
color: @text-color;
|
||||
}
|
||||
}
|
||||
.new-alarm-item-level {
|
||||
width: 52px;
|
||||
padding: 2px 8px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
|
||||
&.level-1 {
|
||||
background-color: #e50012;
|
||||
}
|
||||
|
||||
&.level-2 {
|
||||
background-color: #ff9457;
|
||||
}
|
||||
|
||||
&.level-3 {
|
||||
background-color: #fabd47;
|
||||
}
|
||||
|
||||
&.level-4 {
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
&.level-5 {
|
||||
background-color: #bbb;
|
||||
}
|
||||
}
|
||||
}
|
||||
.empty-body {
|
||||
height: 142px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
width: 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>
|
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div class="top-card">
|
||||
<div class="top-card-content">
|
||||
<div class="content-left">
|
||||
<div class="content-left-title">
|
||||
<span>{{ title }}</span>
|
||||
<a-tooltip placement="top" v-if="tooltip">
|
||||
<template #title>
|
||||
<span>{{ tooltip }}</span>
|
||||
</template>
|
||||
<AIcon type="QuestionCircleOutlined" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="content-left-value">{{ value }}</div>
|
||||
</div>
|
||||
<div class="content-right" v-if="img">
|
||||
<img :src="img" alt="" />
|
||||
</div>
|
||||
<div class="content-right-echart" v-else>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="top-card-footer">
|
||||
<template v-for="(item, index) in footer" :key="index">
|
||||
<span v-if="!item.status">{{ item.title }}</span>
|
||||
<a-badge v-else :text="item.title" :status="item.status" />
|
||||
<div class="footer-item-value">{{ item.value }}</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue';
|
||||
import type { Footer } from '@/views/device/DashBoard/typings'
|
||||
|
||||
const props = defineProps({
|
||||
title: { type: String, default: '' },
|
||||
tooltip: { type: String, default: '' },
|
||||
img: { type: String, default: '' },
|
||||
footer: { type: Array as PropType<Footer[]>, default: '' },
|
||||
value: { type: Number, default: 0 },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.top-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// height: 200px;
|
||||
padding: 24px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #e0e4e8;
|
||||
border-radius: 2px;
|
||||
.top-card-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-grow: 1;
|
||||
justify-content: space-between;
|
||||
.content-left {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
&-title {
|
||||
color: rgba(0, 0, 0, 0.64);
|
||||
}
|
||||
&-value {
|
||||
padding: 12px 0;
|
||||
color: #323130;
|
||||
font-weight: 700;
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
.content-right {
|
||||
width: 0;
|
||||
height: 123px;
|
||||
display: flex;
|
||||
flex-grow: .7;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.content-right-echart{
|
||||
height: 123px;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
.top-card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
.footer-item-value {
|
||||
color: #323130;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,547 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<div class="DashBoardBox">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="6">
|
||||
<TopCard
|
||||
title="今日告警"
|
||||
:value="state.today"
|
||||
:footer="currentMonAlarm"
|
||||
>
|
||||
<Charts :options="state.fifteenOptions"></Charts>
|
||||
</TopCard>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<TopCard
|
||||
title="告警配置"
|
||||
:value="state.config"
|
||||
:footer="alarmState"
|
||||
:img="getImage('/device/device-number.png')"
|
||||
></TopCard>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<NewAlarm :alarm-list="state.alarmList"></NewAlarm>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<div class="alarm-card">
|
||||
<Guide>
|
||||
<template #title>
|
||||
<span style="margin-right: 24px">告警统计</span>
|
||||
<a-select
|
||||
style="width: 40%"
|
||||
v-model:value="queryCodition.targetType"
|
||||
:options="
|
||||
isNoCommunity ? selectOpt1 : selectOpt2
|
||||
"
|
||||
@change="selectChange"
|
||||
></a-select>
|
||||
</template>
|
||||
<template #extra>
|
||||
<TimeSelect
|
||||
key="flow-static"
|
||||
:type="'week'"
|
||||
:quickBtnList="quickBtnList"
|
||||
@change="initQueryTime"
|
||||
/>
|
||||
</template>
|
||||
</Guide>
|
||||
<div class="alarmBox">
|
||||
<div class="alarmStatistics-chart">
|
||||
<Charts
|
||||
:options="alarmStatisticsOption"
|
||||
></Charts>
|
||||
</div>
|
||||
<div class="alarmRank">
|
||||
<h4>告警排名</h4>
|
||||
<ul v-if="state.ranking.length" class="rankingList">
|
||||
<li v-for="(item,i) in state.ranking" :key="item.targetId">
|
||||
<img :src="getImage(`/rule-engine/dashboard/ranking/${i+1}.png`)" alt="">
|
||||
<span class="rankingItemTitle" :title="item.targetName">{{item.targetName}}</span>
|
||||
<span class="rankingItemValue">{{item.count}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else class="empty-body">
|
||||
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE"></a-empty>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Empty } from 'ant-design-vue';
|
||||
import { getImage } from '@/utils/comm';
|
||||
import Charts from './components/Charts.vue';
|
||||
import TopCard from './components/TopCard.vue';
|
||||
import NewAlarm from './components/NewAlarm.vue';
|
||||
import TimeSelect from './components/TimeSelect.vue';
|
||||
import Guide from './components/Guide.vue';
|
||||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import type { SelectTypes } from 'ant-design-vue/es/select';
|
||||
import type { Footer } from '@/views/rule-engine/DashBoard/typings';
|
||||
import { isNoCommunity } from '@/utils/utils';
|
||||
import {
|
||||
dashboard,
|
||||
getAlarm,
|
||||
getAlarmConfigCount,
|
||||
getAlarmLevel,
|
||||
} from '@/api/rule-engine/dashboard';
|
||||
import moment from 'moment';
|
||||
let currentMonAlarm = ref<Footer[]>([
|
||||
{
|
||||
title: '当月告警',
|
||||
value: 0,
|
||||
status: 'success',
|
||||
},
|
||||
]);
|
||||
let alarmState = ref<Footer[]>([
|
||||
{
|
||||
title: '正常',
|
||||
value: 0,
|
||||
status: 'success',
|
||||
},
|
||||
{
|
||||
title: '禁用',
|
||||
value: 0,
|
||||
status: 'error',
|
||||
},
|
||||
]);
|
||||
const selectOpt1 = ref<Object[]>([
|
||||
{ label: '设备', value: 'device' },
|
||||
{ label: '产品', value: 'product' },
|
||||
{ label: '组织', value: 'org' },
|
||||
{ label: '其它', value: 'other' },
|
||||
]);
|
||||
const selectOpt2 = ref<SelectTypes['options']>([
|
||||
{ label: '设备', value: 'device' },
|
||||
{ label: '产品', value: 'product' },
|
||||
{ label: '其它', value: 'other' },
|
||||
]);
|
||||
let queryCodition = reactive({
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
targetType: 'device',
|
||||
});
|
||||
let alarmStatisticsOption = ref<any>({});
|
||||
const quickBtnList = [
|
||||
{ label: '昨日', value: 'yesterday' },
|
||||
{ label: '近一周', value: 'week' },
|
||||
{ label: '近一月', value: 'month' },
|
||||
{ label: '近一年', value: 'year' },
|
||||
];
|
||||
type DashboardItem = {
|
||||
group: string;
|
||||
data: Record<string, any>;
|
||||
};
|
||||
let state = reactive<{
|
||||
today: number;
|
||||
thisMonth: number;
|
||||
config: number;
|
||||
enabledConfig: number;
|
||||
disabledConfig: number;
|
||||
alarmList: any[];
|
||||
ranking: { targetId: string; targetName: string; count: number }[];
|
||||
fifteenOptions: any;
|
||||
}>({
|
||||
today: 0,
|
||||
thisMonth: 0,
|
||||
config: 0,
|
||||
enabledConfig: 0,
|
||||
disabledConfig: 0,
|
||||
alarmList: [],
|
||||
ranking: [],
|
||||
fifteenOptions: {},
|
||||
});
|
||||
// 今日告警
|
||||
const today = {
|
||||
dashboard: 'alarm',
|
||||
object: 'record',
|
||||
measurement: 'trend',
|
||||
dimension: 'agg',
|
||||
group: 'today',
|
||||
params: {
|
||||
time: '1d',
|
||||
// targetType: 'device',
|
||||
format: 'HH:mm:ss',
|
||||
from: moment(new Date(new Date().setHours(0, 0, 0, 0))).format(
|
||||
'YYYY-MM-DD HH:mm:ss',
|
||||
),
|
||||
to: 'now',
|
||||
// limit: 24,
|
||||
},
|
||||
};
|
||||
// 当月告警
|
||||
const thisMonth = {
|
||||
dashboard: 'alarm',
|
||||
object: 'record',
|
||||
measurement: 'trend',
|
||||
dimension: 'agg',
|
||||
group: 'thisMonth',
|
||||
params: {
|
||||
time: '1M',
|
||||
// targetType: 'device',
|
||||
format: 'yyyy-MM',
|
||||
limit: 1,
|
||||
from: 'now-1M',
|
||||
},
|
||||
};
|
||||
|
||||
const fifteen = {
|
||||
dashboard: 'alarm',
|
||||
object: 'record',
|
||||
measurement: 'trend',
|
||||
dimension: 'agg',
|
||||
group: '15day',
|
||||
params: {
|
||||
time: '1d',
|
||||
format: 'yyyy-MM-dd',
|
||||
// targetType: 'product',
|
||||
from: 'now-15d',
|
||||
to: 'now',
|
||||
limit: 15,
|
||||
},
|
||||
};
|
||||
const getDashBoard = () => {
|
||||
dashboard([today, thisMonth, fifteen]).then((res) => {
|
||||
if (res.status == 200) {
|
||||
const _data = res.result as DashboardItem[];
|
||||
state.today = _data.find(
|
||||
(item) => item.group === 'today',
|
||||
)?.data.value;
|
||||
state.thisMonth = _data.find(
|
||||
(item) => item.group === 'thisMonth',
|
||||
)?.data.value;
|
||||
currentMonAlarm.value[0].value = state.thisMonth;
|
||||
const fifteenData = _data
|
||||
.filter((item) => item.group === '15day')
|
||||
.map((item) => item.data)
|
||||
.sort((a, b) => b.timestamp - a.timestamp);
|
||||
state.fifteenOptions = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: fifteenData.map((item) => item.timeString),
|
||||
show: false,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
show: false,
|
||||
},
|
||||
grid: {
|
||||
top: '2%',
|
||||
bottom: 0,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '告警数',
|
||||
data: fifteenData.map((item) => item.value),
|
||||
type: 'bar',
|
||||
itemStyle: {
|
||||
color: '#2F54EB',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
getDashBoard();
|
||||
const getAlarmConfig = async () => {
|
||||
const countRes = await getAlarmConfigCount({});
|
||||
const enabeldRes = await getAlarmConfigCount({
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: 'enabled',
|
||||
},
|
||||
],
|
||||
});
|
||||
const disableRes = await getAlarmConfigCount({
|
||||
terms: [
|
||||
{
|
||||
column: 'state',
|
||||
value: 'disabled',
|
||||
},
|
||||
],
|
||||
});
|
||||
if (countRes.status == 200) {
|
||||
state.config = countRes.result;
|
||||
}
|
||||
if (enabeldRes.status == 200) {
|
||||
state.enabledConfig = enabeldRes.result;
|
||||
alarmState.value[0].value = state.enabledConfig;
|
||||
}
|
||||
if (disableRes.status == 200) {
|
||||
state.disabledConfig = disableRes.result;
|
||||
alarmState.value[1].value = state.disabledConfig;
|
||||
}
|
||||
};
|
||||
getAlarmConfig();
|
||||
const getCurrentAlarm = async () => {
|
||||
const alarmLevel: any = await getAlarmLevel();
|
||||
const sorts = { alarmTime: 'desc' };
|
||||
const currentAlarm: any = await getAlarm(encodeQuery({ sorts }));
|
||||
if (currentAlarm.status === 200) {
|
||||
if (alarmLevel.status === 200) {
|
||||
const levels = alarmLevel.result.levels;
|
||||
state.alarmList = currentAlarm.result?.data
|
||||
.filter((i: any) => i?.state?.value === 'warning')
|
||||
.map((item: { level: any }) => ({
|
||||
...item,
|
||||
levelName: levels.find((l: any) => l.level === item.level)
|
||||
?.title,
|
||||
}));
|
||||
} else {
|
||||
state.alarmList = currentAlarm.result?.data.filter(
|
||||
(item: any) => item?.state?.value === 'warning',
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
getCurrentAlarm();
|
||||
//初始化查询条件
|
||||
const initQueryTime = (data: any) => {
|
||||
queryCodition.startTime = data.start;
|
||||
queryCodition.endTime = data.end;
|
||||
console.log(queryCodition);
|
||||
selectChange();
|
||||
};
|
||||
const selectChange = () => {
|
||||
let time = '1h';
|
||||
let format = 'HH';
|
||||
let limit = 12;
|
||||
const dt = queryCodition.endTime - queryCodition.startTime;
|
||||
const hour = 60 * 60 * 1000;
|
||||
const day = hour * 24;
|
||||
const month = day * 30;
|
||||
const year = 365 * day;
|
||||
if (dt <= day) {
|
||||
limit = Math.abs(Math.ceil(dt / hour));
|
||||
} else if (dt > day && dt < year) {
|
||||
limit = Math.abs(Math.ceil(dt / day)) + 1;
|
||||
time = '1d';
|
||||
format = 'M月dd日';
|
||||
} else if (dt >= year) {
|
||||
limit = Math.abs(Math.floor(dt / month));
|
||||
time = '1M';
|
||||
format = 'yyyy年-M月';
|
||||
}
|
||||
|
||||
// 告警趋势
|
||||
const chartData = {
|
||||
dashboard: 'alarm',
|
||||
object: 'record',
|
||||
measurement: 'trend',
|
||||
dimension: 'agg',
|
||||
group: 'alarmTrend',
|
||||
params: {
|
||||
targetType: queryCodition.targetType, // product、device、org、other
|
||||
format: format,
|
||||
time: time,
|
||||
// from: 'now-1y', // now-1d、now-1w、now-1M、now-1y
|
||||
// to: 'now',
|
||||
limit: limit, // 12
|
||||
// time: params.time.type === 'today' ? '1h' : '1d',
|
||||
from: moment(queryCodition.startTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
to: moment(queryCodition.endTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
// limit: 30,
|
||||
},
|
||||
};
|
||||
|
||||
// 告警排名
|
||||
const order = {
|
||||
dashboard: 'alarm',
|
||||
object: 'record',
|
||||
measurement: 'rank',
|
||||
dimension: 'agg',
|
||||
group: 'alarmRank',
|
||||
params: {
|
||||
// time: '1h',
|
||||
time: time,
|
||||
targetType: queryCodition.targetType,
|
||||
from: moment(queryCodition.startTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
to: moment(queryCodition.endTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
limit: 9,
|
||||
},
|
||||
};
|
||||
let tip = '其它';
|
||||
if (queryCodition.targetType === 'device') {
|
||||
tip = '设备';
|
||||
} else if (queryCodition.targetType === 'product') {
|
||||
tip = '产品';
|
||||
} else if (queryCodition.targetType === 'org') {
|
||||
tip = '组织';
|
||||
}
|
||||
// 网络请求
|
||||
dashboard([chartData, order]).then((res) => {
|
||||
if (res.status == 200) {
|
||||
const xData: string[] = [];
|
||||
const sData: number[] = [];
|
||||
res.result
|
||||
.filter((item: any) => item.group === 'alarmTrend')
|
||||
.forEach((item: any) => {
|
||||
xData.push(item.data.timeString);
|
||||
sData.push(item.data.value);
|
||||
});
|
||||
alarmStatisticsOption.value = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: xData.reverse(),
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
// axisPointer: {
|
||||
// type: 'shadow',
|
||||
// },
|
||||
},
|
||||
grid: {
|
||||
top: '2%',
|
||||
bottom: '5%',
|
||||
left: '24px',
|
||||
right: '48px',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: tip,
|
||||
data: sData.reverse(),
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
color: '#685DEB',
|
||||
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
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
state.ranking = res.result
|
||||
?.filter((item: any) => item.group === 'alarmRank')
|
||||
.map((d: { data: { value: any } }) => d.data?.value)
|
||||
.sort(
|
||||
(a: { count: number }, b: { count: number }) =>
|
||||
b.count - a.count,
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.alarm-card {
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
padding: 24px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.alarmBox {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
.alarmStatistics-chart {
|
||||
width: 70%;
|
||||
height: 500px;
|
||||
}
|
||||
.alarmRank {
|
||||
position: relative;
|
||||
width: 30%;
|
||||
padding-left: 48px;
|
||||
}
|
||||
}
|
||||
.rankingList {
|
||||
margin: 25px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
zoom: 1;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
display: table;
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
&::after {
|
||||
clear: both;
|
||||
height: 0;
|
||||
font-size: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
span {
|
||||
//color: red;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.rankingItemNumber {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 1.5px;
|
||||
margin-right: 16px;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
background-color: #edf0f3;
|
||||
border-radius: 20px;
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
background-color: #314659;
|
||||
}
|
||||
}
|
||||
|
||||
.rankingItemTitle {
|
||||
flex: 1;
|
||||
margin-right: 8px;
|
||||
padding-left: 8px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
.empty-body {
|
||||
height: 490px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
// height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,5 @@
|
|||
export type Footer = {
|
||||
title: string;
|
||||
value: number | string;
|
||||
status?: "default" | "error" | "success" | "warning" | "processing"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<page-container>
|
||||
<a-card>
|
||||
<div>
|
||||
<Search :columns="query.columns" target="device-instance" @search="handleSearch"></Search>
|
||||
<JTable
|
||||
:columns="columns"
|
||||
|
@ -147,7 +147,7 @@
|
|||
:title="title"
|
||||
@success="refresh"
|
||||
/>
|
||||
</a-card>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
|
@ -209,8 +209,8 @@ const columns = [
|
|||
},
|
||||
{
|
||||
title: '说明',
|
||||
dataIndex: 'describe',
|
||||
key: 'describe',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
|
|
|
@ -82,8 +82,8 @@ export default defineConfig(({ mode}) => {
|
|||
// target: 'http://192.168.33.22:8800',
|
||||
// target: 'http://192.168.32.244:8881',
|
||||
// target: 'http://47.112.135.104:5096', // opcua
|
||||
// target: 'http://120.77.179.54:8844', // 120测试
|
||||
target: 'http://47.108.63.174:8845', // 测试
|
||||
target: 'http://120.77.179.54:8844', // 120测试
|
||||
// target: 'http://47.108.63.174:8845', // 测试
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '')
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue