fix: 运行状态详情查看
This commit is contained in:
parent
15be308dc8
commit
25ee85681c
|
@ -37,6 +37,7 @@
|
|||
"unplugin-vue-components": "^0.22.12",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vue": "^3.2.45",
|
||||
"vue-json-viewer": "^3.0.4",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue3-markdown-it": "^1.0.10",
|
||||
"vue3-ts-jsoneditor": "^2.7.1"
|
||||
|
|
|
@ -467,3 +467,19 @@ export const saveEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/
|
|||
* @returns
|
||||
*/
|
||||
export const getPropertyData = (deviceId: string, params: Record<string, unknown>) => server.get(`/device-instance/${deviceId}/properties/_query`, params)
|
||||
|
||||
/**
|
||||
* 聚合查询设备属性
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const getPropertiesInfo = (deviceId: string, data: Record<string, unknown>) => server.post(`/device-instance/${deviceId}/agg/_query`, data)
|
||||
|
||||
/**
|
||||
* 聚合查询设备属性
|
||||
* @param deviceId
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
export const getPropertiesList = (deviceId: string, property: string, data: Record<string, unknown>) => server.post(`/device-instance/${deviceId}/property/${property}/_query`, data)
|
|
@ -4,6 +4,8 @@ import { filterAsnycRouter, MenuItem } from '@/utils/menu'
|
|||
import { isArray } from 'lodash-es'
|
||||
import { usePermissionStore } from './permission'
|
||||
import router from '@/router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { onlyMessage } from '@/utils/comm'
|
||||
|
||||
const defaultOwnParams = [
|
||||
{
|
||||
|
@ -77,6 +79,7 @@ export const useMenuStore = defineStore({
|
|||
name, params, query
|
||||
})
|
||||
} else {
|
||||
onlyMessage('暂无权限,请联系管理员', 'error')
|
||||
console.warn(`没有找到对应的页面: ${name}`)
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import AIcon from "@/components/AIcon";
|
||||
import { useInstanceStore } from "@/store/instance";
|
||||
import { useMenuStore } from "@/store/menu";
|
||||
import { Button, Descriptions, Modal } from "ant-design-vue"
|
||||
import styles from './index.module.less'
|
||||
|
||||
|
@ -14,6 +16,10 @@ const ManualInspection = defineComponent({
|
|||
|
||||
const { data } = props
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const dataRender = () => {
|
||||
if (data.type === 'device' || data.type === 'product') {
|
||||
return (
|
||||
|
@ -207,7 +213,13 @@ const ManualInspection = defineComponent({
|
|||
emit('save', data)
|
||||
}}
|
||||
onCancel={() => {
|
||||
// TODO 跳转设备和产品
|
||||
if (data.type === 'device') {
|
||||
instanceStore.tabActiveKey = 'Info'
|
||||
} else if (data.type === 'product') {
|
||||
menuStory.jumpPage('device/Product/Detail', { id: data.productId, tab: 'access' });
|
||||
} else {
|
||||
menuStory.jumpPage('link/AccessConfig/Detail', { id: data.configuration?.id });
|
||||
}
|
||||
}}>
|
||||
<div style={{ display: 'flex' }}>{dataRender()}</div>
|
||||
</Modal>
|
||||
|
|
|
@ -11,6 +11,8 @@ import _ from "lodash"
|
|||
import DiagnosticAdvice from './DiagnosticAdvice'
|
||||
import ManualInspection from './ManualInspection'
|
||||
import { deployDevice } from "@/api/initHome"
|
||||
import PermissionButton from '@/components/PermissionButton/index.vue'
|
||||
import { useMenuStore } from "@/store/menu"
|
||||
|
||||
type TypeProps = 'network' | 'child-device' | 'media' | 'cloud' | 'channel'
|
||||
|
||||
|
@ -41,6 +43,7 @@ const Status = defineComponent({
|
|||
const diagnoseData = ref<Partial<Record<string, any>>>()
|
||||
|
||||
const bindParentVisible = ref<boolean>(false)
|
||||
const menuStory = useMenuStore();
|
||||
|
||||
const configuration = reactive<{
|
||||
product: Record<string, any>,
|
||||
|
@ -57,19 +60,8 @@ const Status = defineComponent({
|
|||
artificialData.value = params
|
||||
}
|
||||
|
||||
// TODO
|
||||
const jumpAccessConfig = () => {
|
||||
// const purl = getMenuPathByCode(MENUS_CODE['device/Product/Detail']);
|
||||
// if (purl) {
|
||||
// history.push(
|
||||
// `${getMenuPathByParams(MENUS_CODE['device/Product/Detail'], device.productId)}`,
|
||||
// {
|
||||
// tab: 'access',
|
||||
// },
|
||||
// );
|
||||
// } else {
|
||||
// message.error('规则可能有加密处理,请联系管理员');
|
||||
// }
|
||||
menuStory.jumpPage('device/Product/Detail', { id: unref(device).productId, tab: 'access' });
|
||||
};
|
||||
|
||||
const jumpDeviceConfig = () => {
|
||||
|
@ -123,34 +115,40 @@ const Status = defineComponent({
|
|||
<Badge
|
||||
status="default"
|
||||
text={
|
||||
<span>网络组件已禁用,请先<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
const res = await startNetwork(
|
||||
unref(gateway)?.channelId,
|
||||
);
|
||||
if (res.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'network',
|
||||
name: '网络组件',
|
||||
desc: '诊断网络组件配置是否正确,配置错误将导致设备连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm></span>
|
||||
<span>网络组件已禁用,请先
|
||||
<PermissionButton
|
||||
type="link"
|
||||
hasPermission="link/Type:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const res = await startNetwork(
|
||||
unref(gateway)?.channelId,
|
||||
);
|
||||
if (res.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'network',
|
||||
name: '网络组件',
|
||||
desc: '诊断网络组件配置是否正确,配置错误将导致设备连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
启用
|
||||
</PermissionButton>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
) : (
|
||||
<div>
|
||||
<div class={styles.infoItem}>
|
||||
|
@ -287,28 +285,31 @@ const Status = defineComponent({
|
|||
<Badge
|
||||
status="default"
|
||||
text={<span>设备接入网关已禁用,请先
|
||||
<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
const resp = await startGateway(unref(device).accessId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: desc,
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
<PermissionButton
|
||||
hasPermission="link/Type:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await startGateway(unref(device).accessId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: desc,
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>
|
||||
启用
|
||||
</PermissionButton>
|
||||
</span>}
|
||||
/>
|
||||
</div>
|
||||
|
@ -411,28 +412,32 @@ const Status = defineComponent({
|
|||
status="default"
|
||||
text={
|
||||
<span>
|
||||
设备接入网关已禁用,请先<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
const resp = await startGateway(unref(device).accessId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: desc,
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
设备接入网关已禁用,请先
|
||||
<PermissionButton
|
||||
hasPermission="link/AccessConfig:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await startGateway(unref(device).accessId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'gateway',
|
||||
name: '设备接入网关',
|
||||
desc: desc,
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>
|
||||
启用
|
||||
</PermissionButton>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
@ -519,28 +524,32 @@ const Status = defineComponent({
|
|||
status="default"
|
||||
text={
|
||||
<span>
|
||||
网关父设备已禁用,请先<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
const resp = await _deploy(response?.result?.id || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'parent-device',
|
||||
name: '网关父设备',
|
||||
desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
网关父设备已禁用,请先
|
||||
<PermissionButton
|
||||
hasPermission="device/Product:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await _deploy(response?.result?.id || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'parent-device',
|
||||
name: '网关父设备',
|
||||
desc: '诊断网关父设备状态是否正常,禁用或离线将导致连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>
|
||||
启用
|
||||
</PermissionButton>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
@ -623,28 +632,32 @@ const Status = defineComponent({
|
|||
status="default"
|
||||
text={
|
||||
<span>
|
||||
产品已禁用,请<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
const resp = await _deployProduct(unref(device).productId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
产品已禁用,请
|
||||
<PermissionButton
|
||||
hasPermission="device/Product:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await _deployProduct(unref(device).productId || '');
|
||||
if (resp.status === 200) {
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'product',
|
||||
name: '产品状态',
|
||||
desc: '诊断产品状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>
|
||||
启用
|
||||
</PermissionButton>
|
||||
产品
|
||||
</span>
|
||||
}
|
||||
|
@ -695,29 +708,34 @@ const Status = defineComponent({
|
|||
status="default"
|
||||
text={
|
||||
<span>
|
||||
设备已禁用,请<Popconfirm
|
||||
title="确认启用"
|
||||
onConfirm={async () => {
|
||||
const resp = await _deploy(unref(device)?.id || '');
|
||||
if (resp.status === 200) {
|
||||
instanceStore.current.state = { value: 'offline', text: '离线' }
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
设备已禁用,请
|
||||
<PermissionButton
|
||||
hasPermission="device/Instance:action"
|
||||
popConfirm={{
|
||||
title: '确认启用',
|
||||
onConfirm: async () => {
|
||||
const resp = await _deploy(unref(device)?.id || '');
|
||||
if (resp.status === 200) {
|
||||
instanceStore.current.state = { value: 'offline', text: '离线' }
|
||||
message.success('操作成功!');
|
||||
list.value = modifyArrayList(
|
||||
list.value,
|
||||
{
|
||||
key: 'device',
|
||||
name: '设备状态',
|
||||
desc: '诊断设备状态是否正常,禁用状态将导致设备连接失败',
|
||||
status: 'success',
|
||||
text: '正常',
|
||||
info: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button type="link" style="padding: 0">启用</Button>
|
||||
</Popconfirm>设备
|
||||
启用
|
||||
</PermissionButton>
|
||||
设备
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
:columns="columns"
|
||||
:request="_getEventList"
|
||||
model="TABLE"
|
||||
:bodyStyle="{padding: '0 24px'}"
|
||||
:bodyStyle="{ padding: '0 24px' }"
|
||||
>
|
||||
<template #timestamp="slotProps">
|
||||
{{ moment(slotProps.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
|
@ -19,18 +19,18 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import moment from 'moment'
|
||||
import { getEventList } from '@/api/device/instance'
|
||||
import { useInstanceStore } from '@/store/instance'
|
||||
import { Modal } from 'ant-design-vue'
|
||||
import moment from 'moment';
|
||||
import { getEventList } from '@/api/device/instance';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
const events = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
const instanceStore = useInstanceStore()
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const columns = ref<Record<string, any>>([
|
||||
{
|
||||
|
@ -38,43 +38,52 @@ const columns = ref<Record<string, any>>([
|
|||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
scopedSlots: true,
|
||||
search: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
}
|
||||
])
|
||||
const params = ref<Record<string, any>>({})
|
||||
},
|
||||
]);
|
||||
const params = ref<Record<string, any>>({});
|
||||
|
||||
const _getEventList = () => getEventList(instanceStore.current.id || '', events.data.id || '', params.value)
|
||||
const _getEventList = () =>
|
||||
getEventList(
|
||||
instanceStore.current.id || '',
|
||||
events.data.id || '',
|
||||
params.value,
|
||||
);
|
||||
|
||||
watchEffect(() => {
|
||||
if(events.data?.valueType?.type === 'object'){
|
||||
if (events.data?.valueType?.type === 'object') {
|
||||
(events.data.valueType?.properties || []).map((i: any) => {
|
||||
columns.value.splice(0, 0, {
|
||||
key: i.id,
|
||||
title: i.name,
|
||||
dataIndex: `${i.id}_format`
|
||||
})
|
||||
})
|
||||
key: i.id,
|
||||
title: i.name,
|
||||
dataIndex: `${i.id}_format`,
|
||||
search: {
|
||||
type: 'string',
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
columns.value.splice(0, 0, {
|
||||
title: '数据',
|
||||
dataIndex: 'value',
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const detail = () => {
|
||||
Modal.info({
|
||||
title: () => '详情',
|
||||
width: 850,
|
||||
content: () => h('div', {}, [
|
||||
h('p', '暂未开发'),
|
||||
]),
|
||||
okText: '关闭'
|
||||
});
|
||||
}
|
||||
content: () => h('div', {}, [h('p', '暂未开发')]),
|
||||
okText: '关闭',
|
||||
});
|
||||
};
|
||||
</script>
|
|
@ -1,3 +1,54 @@
|
|||
<!-- 坐标点拾取组件 -->
|
||||
<template>
|
||||
<div>AMap</div>
|
||||
<div style="width: 100%; height: 400px">
|
||||
<div style="position: relative">
|
||||
<div style="position: absolute; right: 0; top: 5px; z-index: 999">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="start">开始动画</a-button>
|
||||
<a-button type="primary" @click="stop">停止动画</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<el-amap :center="center" :zooms="[3, 20]" @init="initMap" ref="map"></el-amap>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { initAMapApiLoader } from '@vuemap/vue-amap';
|
||||
import AMapUI from '@vuemap/vue-amap'
|
||||
import '@vuemap/vue-amap/dist/style.css';
|
||||
|
||||
initAMapApiLoader({
|
||||
key: 'a0415acfc35af15f10221bfa5a6850b4',
|
||||
securityJsCode: 'cae6108ec3dd222f946d1a7237c78be0',
|
||||
});
|
||||
|
||||
interface EmitProps {
|
||||
(e: 'update:points', data: string): void;
|
||||
}
|
||||
const props = defineProps({
|
||||
points: { type: Array, default: () => [] },
|
||||
});
|
||||
const emit = defineEmits<EmitProps>();
|
||||
|
||||
// 地图拾取的坐标点(经纬度字符串)
|
||||
const mapPoint = ref('');
|
||||
|
||||
const map = ref(null);
|
||||
|
||||
const center = ref([106.55, 29.56]);
|
||||
const marker = ref(null);
|
||||
|
||||
/**
|
||||
* 地图初始化
|
||||
* @param e
|
||||
*/
|
||||
const initMap = (e: any) => {
|
||||
console.log(e)
|
||||
// map = e;
|
||||
// const pointStr = mapPoint.value as string;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
||||
|
|
|
@ -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>
|
|
@ -1,3 +1,218 @@
|
|||
<template>
|
||||
<div>Charts</div>
|
||||
<a-spin :spinning="loading">
|
||||
<div>
|
||||
<a-space>
|
||||
<div>
|
||||
统计周期:
|
||||
<a-select v-model:value="cycle" style="width: 120px">
|
||||
<a-select-option value="*" v-if="_type"
|
||||
>实际值</a-select-option
|
||||
>
|
||||
<a-select-option value="1m">按分钟统计</a-select-option>
|
||||
<a-select-option value="1h">按小时统计</a-select-option>
|
||||
<a-select-option value="1d">按天统计</a-select-option>
|
||||
<a-select-option value="1w">按周统计</a-select-option>
|
||||
<a-select-option value="1M">按月统计</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<div v-if="cycle !== '*' && _type">
|
||||
统计规则:
|
||||
<a-select v-model:value="agg" style="width: 120px">
|
||||
<a-select-option value="AVG">平均值</a-select-option>
|
||||
<a-select-option value="MAX">最大值</a-select-option>
|
||||
<a-select-option value="MIN">最小值</a-select-option>
|
||||
<a-select-option value="COUNT">总数</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
<div style="width: 100%; height: 500px">
|
||||
<Chart :options="options" v-if="chartsList.length" />
|
||||
<JEmpty v-else />
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getPropertiesInfo, getPropertiesList } from '@/api/device/instance';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import Chart from './Chart.vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const list = ['int', 'float', 'double', 'long'];
|
||||
|
||||
const prop = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
time: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const cycle = ref<string>('*');
|
||||
const agg = ref<string>('AVG');
|
||||
const loading = ref<boolean>(false);
|
||||
const chartsList = ref<any[]>([]);
|
||||
const instanceStore = useInstanceStore();
|
||||
const options = ref({});
|
||||
|
||||
const _type = computed(() => {
|
||||
const flag = list.includes(prop.data?.valueType?.type || '')
|
||||
cycle.value = flag ? '*' : '1m'
|
||||
return flag
|
||||
});
|
||||
|
||||
const queryChartsAggList = async () => {
|
||||
loading.value = true;
|
||||
const resp = await getPropertiesInfo(instanceStore.current.id, {
|
||||
columns: [
|
||||
{
|
||||
property: prop.data.id,
|
||||
alias: prop.data.id,
|
||||
agg: agg.value,
|
||||
},
|
||||
],
|
||||
query: {
|
||||
interval: cycle.value,
|
||||
format: 'yyyy-MM-dd HH:mm:ss',
|
||||
from: prop.time[0],
|
||||
to: prop.time[1],
|
||||
},
|
||||
});
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
const dataList: any[] = [
|
||||
{
|
||||
year: prop.time[1],
|
||||
value: undefined,
|
||||
type: prop.data?.name || '',
|
||||
},
|
||||
];
|
||||
(resp.result as any[]).forEach((i: any) => {
|
||||
dataList.push({
|
||||
...i,
|
||||
year: i.time,
|
||||
value: Number(i[prop.data.id || '']),
|
||||
type: prop.data?.name || '',
|
||||
});
|
||||
});
|
||||
dataList.push({
|
||||
year: prop.time[0],
|
||||
value: undefined,
|
||||
type: prop.data?.name || '',
|
||||
});
|
||||
chartsList.value = (dataList || []).reverse();
|
||||
}
|
||||
};
|
||||
|
||||
const queryChartsList = async () => {
|
||||
loading.value = true;
|
||||
const resp = await getPropertiesList(
|
||||
instanceStore.current.id,
|
||||
prop.data.id,
|
||||
{
|
||||
paging: false,
|
||||
terms: [
|
||||
{
|
||||
column: 'timestamp$BTW',
|
||||
value:
|
||||
prop.time[0] && prop.time[1]
|
||||
? [prop.time[0], prop.time[1]]
|
||||
: [],
|
||||
type: 'and',
|
||||
},
|
||||
],
|
||||
sorts: [{ name: 'timestamp', order: 'asc' }],
|
||||
},
|
||||
);
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
const dataList: any[] = [
|
||||
{
|
||||
year: prop.time[0],
|
||||
value: undefined,
|
||||
type: prop.data?.name || '',
|
||||
},
|
||||
];
|
||||
(resp.result as any)?.data?.forEach((i: any) => {
|
||||
dataList.push({
|
||||
...i,
|
||||
year: i.timestamp,
|
||||
value: i.value,
|
||||
type: prop.data?.name || '',
|
||||
});
|
||||
});
|
||||
dataList.push({
|
||||
year: prop.time[1],
|
||||
value: undefined,
|
||||
type: prop.data?.name || '',
|
||||
});
|
||||
chartsList.value = dataList || [];
|
||||
}
|
||||
};
|
||||
|
||||
const getOptions = (arr: any[]) => {
|
||||
options.value = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: arr.map((item) => {
|
||||
return echarts.format.formatTime(
|
||||
'yyyy-MM-dd\nhh:mm:ss',
|
||||
item.year,
|
||||
false,
|
||||
);
|
||||
}),
|
||||
name: '时间',
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: arr[0]?.type,
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
start: 0,
|
||||
end: 10,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
end: 10,
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
position: function (pt: any) {
|
||||
return [pt[0], '10%'];
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: arr.map((i: any) => i.value),
|
||||
type: 'line',
|
||||
areaStyle: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
watch(
|
||||
() => [cycle, agg],
|
||||
([newCycle, newAgg]) => {
|
||||
if (newCycle.value === '*' && _type.value) {
|
||||
queryChartsList();
|
||||
} else {
|
||||
queryChartsAggList();
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
watchEffect(() => {
|
||||
if (chartsList.value.length) {
|
||||
getOptions(chartsList.value);
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<div style="position: relative">
|
||||
<div style="position: absolute; right: 0; top: 5px; z-index: 999">
|
||||
<a-space>
|
||||
<a-button type="primary">开始动画</a-button>
|
||||
<a-button type="primary">停止动画</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<AMap :points="geoList" />
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getPropertyData } from '@/api/device/instance';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import AMap from './AMap.vue';
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const prop = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
time: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const geoList = ref<any[]>([]);
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const query = async () => {
|
||||
loading.value = true;
|
||||
const resp = await getPropertyData(
|
||||
instanceStore.current.id,
|
||||
encodeQuery({
|
||||
paging: false,
|
||||
terms: {
|
||||
property: prop.data.id,
|
||||
timestamp$BTW: prop.time[0] && prop.time[1] ? prop.time : [],
|
||||
},
|
||||
sorts: { timestamp: 'asc' },
|
||||
}),
|
||||
);
|
||||
loading.value = false;
|
||||
if (resp.status === 200) {
|
||||
const list: any[] = [];
|
||||
((resp.result as any)?.data || []).forEach((item: any) => {
|
||||
list.push([item.value.lon, item.value.lat]);
|
||||
});
|
||||
geoList.value = list
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => [prop.data.id, prop.time],
|
||||
([newVal]) => {
|
||||
if (newVal) {
|
||||
query();
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true, immediate: true
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
|
@ -18,6 +18,13 @@
|
|||
<template v-if="column.key === 'timestamp'">
|
||||
{{ moment(record.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</template>
|
||||
<template v-if="column.key === 'value'">
|
||||
<ValueRender
|
||||
type="table"
|
||||
:data="_props.data"
|
||||
:value="{ formatValue: record.value }"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button
|
||||
|
@ -30,7 +37,7 @@
|
|||
@click="_download(record)"
|
||||
><AIcon type="DownloadOutlined"
|
||||
/></a-button>
|
||||
<a-button type="link"
|
||||
<a-button type="link" @click="showDetail(record)"
|
||||
><AIcon type="SearchOutlined"
|
||||
/></a-button>
|
||||
</a-space>
|
||||
|
@ -38,6 +45,28 @@
|
|||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
<a-modal
|
||||
title="详情"
|
||||
:visible="visible"
|
||||
@ok="visible = false"
|
||||
@cancel="visible = false"
|
||||
>
|
||||
<div>自定义属性</div>
|
||||
<JsonViewer
|
||||
v-if="
|
||||
data?.valueType?.type === 'object' ||
|
||||
data?.valueType?.type === 'array'
|
||||
"
|
||||
:expand-depth="5"
|
||||
:value="current.formatValue"
|
||||
/>
|
||||
<a-textarea
|
||||
v-else-if="data?.valueType?.type === 'file'"
|
||||
:value="current.formatValue"
|
||||
:row="3"
|
||||
/>
|
||||
<a-input v-else disabled :value="current.formatValue" />
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -46,6 +75,8 @@ import { useInstanceStore } from '@/store/instance';
|
|||
import encodeQuery from '@/utils/encodeQuery';
|
||||
import moment from 'moment';
|
||||
import { getType } from '../index';
|
||||
import ValueRender from '../ValueRender.vue';
|
||||
import JsonViewer from 'vue-json-viewer';
|
||||
|
||||
const _props = defineProps({
|
||||
data: {
|
||||
|
@ -57,8 +88,11 @@ const _props = defineProps({
|
|||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
const dataSource = ref({});
|
||||
const current = ref<any>({});
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const columns = computed(() => {
|
||||
const arr: any[] = [
|
||||
|
@ -92,6 +126,11 @@ const showLoad = computed(() => {
|
|||
);
|
||||
});
|
||||
|
||||
const showDetail = (item: any) => {
|
||||
visible.value = true;
|
||||
current.value = item;
|
||||
};
|
||||
|
||||
const queryPropertyData = async (params: any) => {
|
||||
const resp = await getPropertyData(
|
||||
instanceStore.current.id,
|
||||
|
@ -99,7 +138,7 @@ const queryPropertyData = async (params: any) => {
|
|||
...params,
|
||||
terms: {
|
||||
property: _props.data.id,
|
||||
timestamp$BTW: _props.time?.length ? _props.time : [],
|
||||
timestamp$BTW: _props.time,
|
||||
},
|
||||
sorts: { timestamp: 'desc' },
|
||||
}),
|
||||
|
@ -109,14 +148,20 @@ const queryPropertyData = async (params: any) => {
|
|||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
if (_props.data.id) {
|
||||
queryPropertyData({
|
||||
pageSize: _props.data.valueType?.type === 'file' ? 5 : 10,
|
||||
pageIndex: 0,
|
||||
});
|
||||
watch(
|
||||
() => [_props.data.id, _props.time],
|
||||
([newVal]) => {
|
||||
if (newVal) {
|
||||
queryPropertyData({
|
||||
pageSize: _props.data.valueType?.type === 'file' ? 5 : 10,
|
||||
pageIndex: 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true, immediate: true
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
const onChange = (page: any) => {
|
||||
queryPropertyData({
|
||||
|
@ -141,3 +186,9 @@ const _download = (record: any) => {
|
|||
document.body.removeChild(downNode);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-pagination-item) {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
|
@ -2,15 +2,15 @@
|
|||
<a-modal title="详情" visible width="50vw" @ok="onCancel" @cancel="onCancel">
|
||||
<div style="margin-bottom: 10px"><TimeComponent v-model="dateValue" /></div>
|
||||
<div>
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tabs v-model:activeKey="activeKey" style="max-height: 600px; overflow-y: auto">
|
||||
<a-tab-pane key="table" tab="列表">
|
||||
<Table :data="props.data" :time="_getTimes" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="charts" tab="图表">
|
||||
<Charts />
|
||||
<Charts :data="props.data" :time="_getTimes" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="geo" tab="轨迹">
|
||||
<AMap />
|
||||
<a-tab-pane key="geo" tab="轨迹" v-if="data?.valueType?.type === 'geoPoint'">
|
||||
<PropertyAMap :data="props.data" :time="_getTimes" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@
|
|||
import type { Dayjs } from 'dayjs';
|
||||
import TimeComponent from './TimeComponent.vue'
|
||||
import Charts from './Charts.vue'
|
||||
import AMap from './AMap.vue'
|
||||
import PropertyAMap from './PropertyAMap.vue'
|
||||
import Table from './Table.vue'
|
||||
|
||||
const props = defineProps({
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<template>
|
||||
<a-card :hoverable="true" class="card-box">
|
||||
<!-- <a-spin :spinning="loading"> -->
|
||||
<div class="card-container">
|
||||
<div class="header">
|
||||
<div class="title">{{ _props.data.name }}</div>
|
||||
<div class="extra">
|
||||
<a-space :size="16">
|
||||
<div class="card-container">
|
||||
<div class="header">
|
||||
<div class="title">{{ _props.data.name }}</div>
|
||||
<div class="extra">
|
||||
<a-space :size="16">
|
||||
<template v-for="i in actions" :key="i.key">
|
||||
<a-tooltip
|
||||
v-for="i in actions"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
v-if="i.key !== 'edit'"
|
||||
>
|
||||
<a-button
|
||||
style="padding: 0; margin: 0"
|
||||
|
@ -17,26 +17,48 @@
|
|||
:disabled="i.disabled"
|
||||
@click="i.onClick && i.onClick(data)"
|
||||
>
|
||||
<AIcon :type="i.icon" style="color: #323130; font-size: 12px" />
|
||||
<AIcon
|
||||
:type="i.icon"
|
||||
style="color: #323130; font-size: 12px"
|
||||
/>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
<ValueRender :data="data" :value="_props.data" type="card" />
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div style="color: rgba(0, 0, 0, .65); font-size: 12px">更新时间</div>
|
||||
<div class="time-value">{{_props?.data?.timeString || '--'}}</div>
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
v-else
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="i.tooltip"
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
type="link"
|
||||
style="padding: 0px"
|
||||
:hasPermission="'device/Instance:update'"
|
||||
>
|
||||
<template #icon
|
||||
><AIcon :type="i.icon" style="color: #323130; font-size: 12px"
|
||||
/></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
<ValueRender :data="data" :value="_props.data" type="card" />
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div style="color: rgba(0, 0, 0, 0.65); font-size: 12px">
|
||||
更新时间
|
||||
</div>
|
||||
<div class="time-value">
|
||||
{{ _props?.data?.timeString || '--' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </a-spin> -->
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ValueRender from './ValueRender.vue'
|
||||
import ValueRender from './ValueRender.vue';
|
||||
const _props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
|
@ -44,7 +66,7 @@ const _props = defineProps({
|
|||
},
|
||||
actions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
// const loading = ref<boolean>(true);
|
||||
|
|
|
@ -13,23 +13,18 @@
|
|||
<a-image :src="value?.formatValue" />
|
||||
</template>
|
||||
<template v-else-if="['.flv', '.m3u8', '.mp4'].includes(type)">
|
||||
<!-- TODO 视频组件缺失 -->
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- <json-viewer
|
||||
:value="{
|
||||
'id': '123'
|
||||
}"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
></json-viewer> -->
|
||||
<JsonViewer
|
||||
:expand-depth="5"
|
||||
:value="value?.formatValue"
|
||||
/>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// import JsonViewer from 'vue3-json-viewer';
|
||||
import JsonViewer from 'vue-json-viewer';
|
||||
|
||||
const _data = defineProps({
|
||||
type: {
|
||||
|
@ -46,9 +41,6 @@ const handleCancel = () => {
|
|||
_emit('close');
|
||||
};
|
||||
|
||||
// watchEffect(() => {
|
||||
// console.log(_data.value?.formatValue)
|
||||
// })
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="value">
|
||||
<div v-if="value?.formatValue !== 0 && !value?.formatValue" :class="valueClass">--</div>
|
||||
<div v-else-if="data?.valueType?.type === 'file'">
|
||||
<div v-else-if="_data.data?.valueType?.type === 'file'">
|
||||
<template v-if="data?.valueType?.fileType === 'base64'">
|
||||
<div :class="valueClass" v-if="!!getType(value?.formatValue)">
|
||||
<img :src="imgMap.get(_type)" @error="onError" />
|
||||
|
@ -36,10 +36,10 @@
|
|||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else-if="data?.valueType?.type === 'object'" @click="getDetail('obj')" :class="valueClass">
|
||||
<div v-else-if="_data.data?.valueType?.type === 'object'" @click="getDetail('obj')" :class="valueClass">
|
||||
<img :src="imgMap.get('obj')" />
|
||||
</div>
|
||||
<div v-else-if="data?.valueType?.type === 'geoPoint' || data?.valueType?.type === 'array'" :class="valueClass">
|
||||
<div v-else-if="_data.data?.valueType?.type === 'geoPoint' || _data.data?.valueType?.type === 'array'" :class="valueClass">
|
||||
{{JSON.stringify(value?.formatValue)}}
|
||||
</div>
|
||||
<div v-else :class="valueClass">
|
||||
|
@ -53,7 +53,7 @@
|
|||
import { getImage } from "@/utils/comm";
|
||||
import { message } from "ant-design-vue";
|
||||
import ValueDetail from './ValueDetail.vue'
|
||||
import {getType, imgMap} from './index'
|
||||
import {getType, imgMap, imgList, videoList, fileList} from './index'
|
||||
|
||||
const _data = defineProps({
|
||||
data: {
|
||||
|
@ -115,7 +115,6 @@ const getDetail = (_type: string) => {
|
|||
_types.value = flag
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -32,20 +32,30 @@
|
|||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
:key="i.key"
|
||||
v-bind="i.tooltip"
|
||||
>
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
<template v-for="i in getActions(slotProps)" :key="i.key">
|
||||
<a-tooltip v-bind="i.tooltip" v-if="i.key !== 'edit'">
|
||||
<a-button
|
||||
style="padding: 0"
|
||||
type="link"
|
||||
:disabled="i.disabled"
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
>
|
||||
<AIcon :type="i.icon" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<PermissionButton
|
||||
:disabled="i.disabled"
|
||||
v-else
|
||||
:popConfirm="i.popConfirm"
|
||||
:tooltip="i.tooltip"
|
||||
@click="i.onClick && i.onClick(slotProps)"
|
||||
type="link"
|
||||
style="padding: 0px"
|
||||
:hasPermission="'device/Instance:update'"
|
||||
>
|
||||
<AIcon :type="i.icon" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<template #icon><AIcon :type="i.icon" /></template>
|
||||
</PermissionButton>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #paginationRender>
|
||||
|
@ -76,7 +86,11 @@
|
|||
@close="indicatorVisible = false"
|
||||
:data="currentInfo"
|
||||
/>
|
||||
<Detail v-if="detailVisible" :data="currentInfo" @close="detailVisible = false" />
|
||||
<Detail
|
||||
v-if="detailVisible"
|
||||
:data="currentInfo"
|
||||
@close="detailVisible = false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -240,11 +254,15 @@ const subscribeProperty = () => {
|
|||
?.pipe(map((res: any) => res.payload))
|
||||
.subscribe((payload) => {
|
||||
list.value = [...list.value, payload];
|
||||
unref(list).sort((a: any, b: any) => a.timestamp - b.timestamp)
|
||||
.forEach((item: any) => {
|
||||
const { value } = item;
|
||||
propertyValue.value[value?.property] = { ...item, ...value };
|
||||
});
|
||||
unref(list)
|
||||
.sort((a: any, b: any) => a.timestamp - b.timestamp)
|
||||
.forEach((item: any) => {
|
||||
const { value } = item;
|
||||
propertyValue.value[value?.property] = {
|
||||
...item,
|
||||
...value,
|
||||
};
|
||||
});
|
||||
// list.value = [...list.value, payload];
|
||||
// throttle(valueChange(list.value), 500);
|
||||
});
|
||||
|
@ -337,8 +355,8 @@ const onSearch = () => {
|
|||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
subRef.value && subRef.value?.unsubscribe()
|
||||
})
|
||||
subRef.value && subRef.value?.unsubscribe();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<page-container
|
||||
:tabList="list"
|
||||
@back="onBack"
|
||||
:tabActiveKey="instanceStore.active"
|
||||
:tabActiveKey="instanceStore.tabActiveKey"
|
||||
@tabChange="onTabChange"
|
||||
>
|
||||
<template #title>
|
||||
|
|
|
@ -155,13 +155,12 @@
|
|||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<h3
|
||||
class="card-item-content-title"
|
||||
@click.stop="handleView(slotProps.id)"
|
||||
>
|
||||
{{ slotProps.name }}
|
||||
</h3>
|
||||
<a-row>
|
||||
<Ellipsis style="width: calc(100% - 100px)">
|
||||
<span style="font-size: 16px; font-weight: 600" @click.stop="handleView(slotProps.id)">
|
||||
{{ slotProps.name }}
|
||||
</span>
|
||||
</Ellipsis>
|
||||
<a-row style="margin-top: 20px">
|
||||
<a-col :span="12">
|
||||
<div class="card-item-content-text">
|
||||
设备类型
|
||||
|
@ -172,7 +171,9 @@
|
|||
<div class="card-item-content-text">
|
||||
产品名称
|
||||
</div>
|
||||
<div>{{ slotProps.productName }}</div>
|
||||
<Ellipsis style="width: 100%">
|
||||
{{ slotProps.productName }}
|
||||
</Ellipsis>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
|
Loading…
Reference in New Issue