fix: 北向输出权限按钮

This commit is contained in:
100011797 2023-02-24 09:26:24 +08:00
parent 34d870f1ec
commit 8b66147fa6
16 changed files with 372 additions and 365 deletions

View File

@ -458,4 +458,12 @@ export const treeEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/
* @param data
* @returns
*/
export const saveEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/operations/${deviceId}/device-collector-save/invoke`, data)
export const saveEdgeMap = (deviceId: string, data?: any) => server.post(`/edge/operations/${deviceId}/device-collector-save/invoke`, data)
/**
*
* @param deviceId
* @param params
* @returns
*/
export const getPropertyData = (deviceId: string, params: Record<string, unknown>) => server.get(`/device-instance/${deviceId}/properties/_query`, params)

View File

@ -52,7 +52,8 @@ const iconKeys = [
'RightOutlined',
'FileTextOutlined',
'UploadOutlined',
'ArrowLeftOutlined'
'ArrowLeftOutlined',
'DownloadOutlined'
]
const Icon = (props: {type: string}) => {

View File

@ -1,287 +0,0 @@
<template>
<a-spin :spinning="loading">
<div class="jtable-body">
<div class="jtable-body-header">
<div class="jtable-body-header-left">
<slot name="headerTitle"></slot>
</div>
<div class="jtable-body-header-right" v-if="!model">
<div class="jtable-setting-item" :class="[ModelEnum.CARD === _model ? 'active' : '']" @click="modelChange(ModelEnum.CARD)">
<AppstoreOutlined />
</div>
<div class="jtable-setting-item" :class="[ModelEnum.TABLE === _model ? 'active' : '']" @click="modelChange(ModelEnum.TABLE)">
<UnorderedListOutlined />
</div>
</div>
</div>
<div class="jtable-content">
<div class="jtable-alert" v-if="rowSelection && rowSelection.selectedRowKeys && rowSelection.selectedRowKeys.length">
<a-alert :message="'已选择' + rowSelection.selectedRowKeys.length + '项'" type="info" :afterClose="handleAlertClose">
<template #closeText>
<a>取消选择</a>
</template>
</a-alert>
</div>
<div v-if="_model === ModelEnum.CARD" class="jtable-card">
<div
v-if="_dataSource.length"
class="jtable-card-items"
:style="{gridTemplateColumns: `repeat(${column}, 1fr)`}"
>
<div
class="jtable-card-item"
v-for="(item, index) in _dataSource"
:key="index"
>
<slot name="card" v-bind="item" :index="index"></slot>
</div>
</div>
<div v-else>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" />
</div>
</div>
<div v-else>
<a-table rowKey="operationId" :rowSelection="rowSelection" :columns="[..._columns]" :dataSource="_dataSource" :pagination="false">
<template #bodyCell="{ column, record }">
<!-- <template v-if="column.key === 'action'">
<a-space>
<a-tooltip v-for="i in actions" :key="i.key" v-bind="i.tooltip">
<a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm">
<a><AIcon :type="i.icon" /></a>
</a-popconfirm>
<a v-else @click="i.onClick && i.onClick(record)">
<AIcon :type="i.icon" />
</a>
</a-tooltip>
</a-space>
</template> -->
<template v-if="column.scopedSlots">
<slot :name="column.key" :row="record"></slot>
</template>
</template>
</a-table>
</div>
</div>
<div class="jtable-pagination" v-if="_dataSource.length && !noPagination">
<a-pagination
size="small"
:total="total"
:showQuickJumper="false"
:showSizeChanger="true"
v-model:current="pageIndex"
v-model:page-size="pageSize"
:show-total="(total, range) => `第 ${range[0]} - ${range[1]} 条/总共 ${total} 条`"
@change="pageChange"
:page-size-options="[12, 24, 48, 60, 100]"
/>
</div>
</div>
</a-spin>
</template>
<script setup lang="ts" name="JTable">
import { UnorderedListOutlined, AppstoreOutlined } from '@ant-design/icons-vue'
import type { TableProps, ColumnsType } from 'ant-design-vue/es/table'
import type { TooltipProps } from 'ant-design-vue/es/tooltip'
import type { PopconfirmProps } from 'ant-design-vue/es/popconfirm'
import { Empty } from 'ant-design-vue'
import { CSSProperties } from 'vue';
enum ModelEnum {
TABLE = 'TABLE',
CARD = 'CARD',
}
type RequestData = {
code: string;
result: {
data: Record<string, any>[] | undefined;
pageIndex: number;
pageSize: number;
total: number;
};
status: number;
} & Record<string, any>;
export interface ActionsType {
key: string;
text?: string;
disabled?: boolean;
permission?: boolean;
onClick?: (data: any) => void;
style?: CSSProperties;
tooltip?: TooltipProps;
popConfirm?: PopconfirmProps;
icon?: string;
}
export interface JColumnsProps extends ColumnsType{
scopedSlots?: boolean; // true: false:
}
export interface JTableProps extends TableProps{
request?: (params: Record<string, any> & {
pageSize: number;
pageIndex: number;
}) => Promise<Partial<RequestData>>;
cardBodyClass?: string;
columns: JColumnsProps;
params?: Record<string, any> & {
pageSize: number;
pageIndex: number;
};
model?: keyof typeof ModelEnum | undefined; // tablecard
actions?: ActionsType[];
noPagination?: boolean;
rowSelection?: TableProps['rowSelection'];
cardProps?: Record<string, any>;
dataSource?: Record<string, any>[];
}
// props
const props = withDefaults(defineProps<JTableProps>(), {
cardBodyClass: '',
request: undefined,
})
// emit
const emit = defineEmits<{
(e: 'cancelSelect'): void
}>()
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE
const _model = ref<keyof typeof ModelEnum>(props.model ? props.model : ModelEnum.CARD); //
const column = ref<number>(4);
const _dataSource = ref<Record<string, any>[]>([])
const pageIndex = ref<number>(0)
const pageSize = ref<number>(6)
const total = ref<number>(0)
const _columns = ref<Record<string, any>[]>([...props.columns])
const loading = ref<boolean>(true)
//
//
const modelChange = (type: keyof typeof ModelEnum) => {
_model.value = type
}
/**
* 请求数据
*/
const handleSearch = async (_params?: Record<string, any>) => {
loading.value = true
if(props.request) {
const resp = await props.request({
pageSize: 12,
pageIndex: 1,
..._params
})
if(resp.status === 200){
//
if(resp.result?.data?.length === 0 && resp.result.total && resp.result.pageSize && resp.result.pageIndex) {
handleSearch({
..._params,
pageSize: pageSize.value,
pageIndex: pageIndex.value - 1,
})
} else {
_dataSource.value = resp.result?.data || []
pageIndex.value = resp.result?.pageIndex || 0
pageSize.value = resp.result?.pageSize || 6
total.value = resp.result?.total || 0
}
}
} else {
_dataSource.value = props?.dataSource || []
}
loading.value = false
}
/**
* 页码变化
*/
const pageChange = (page: number, size: number) => {
handleSearch({
...props.params,
pageSize: size,
pageIndex: pageSize.value === size ? page : 1,
})
}
// alert
const handleAlertClose = () => {
emit('cancelSelect')
}
// watchEffect(() => {
// if(Array.isArray(props.actions) && props.actions.length) {
// _columns.value = [...props.columns,
// {
// title: '',
// key: 'action',
// fixed: 'right',
// width: 250
// }
// ]
// } else {
// _columns.value = [...props.columns]
// }
// })
watchEffect(() => {
handleSearch(props.params)
})
// TODO
</script>
<style lang="less" scoped>
.jtable-body {
width: 100%;
padding: 0 24px 24px;
background-color: white;
.jtable-body-header {
padding: 16px 0;
display: flex;
justify-content: space-between;
align-items: center;
.jtable-body-header-right {
display: flex;
gap: 8px;
.jtable-setting-item {
color: rgba(0, 0, 0, 0.75);
font-size: 16px;
cursor: pointer;
&:hover {
color: @primary-color-hover;
}
&.active {
color: @primary-color-active;
}
}
}
}
.jtable-content {
.jtable-alert {
margin-bottom: 16px;
}
.jtable-card {
.jtable-card-items {
display: grid;
grid-gap: 26px;
.jtable-card-item {
display: flex;
}
}
}
}
.jtable-pagination {
margin-top: 20px;
display: flex;
justify-content: flex-end;
/deep/ .ant-pagination-item {
display: none !important;
}
}
}
</style>

View File

@ -1,6 +1,10 @@
<template>
<page-container>
<Search :columns="columns" target="northbound-aliyun" @search="handleSearch" />
<Search
:columns="columns"
target="northbound-aliyun"
@search="handleSearch"
/>
<JTable
ref="instanceRef"
:columns="columns"
@ -24,22 +28,15 @@
<CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
enabled: 'success',
disabled: 'error'
disabled: 'error',
}"
>
<template #img>
<slot name="img">
<img
:src="
getImage('/northbound/aliyun.png')
"
/>
</slot>
<img :src="getImage('/northbound/aliyun.png')" />
</template>
<template #content>
<h3
@ -114,23 +111,18 @@
</template>
<script setup lang="ts">
import {
query,
_undeploy,
_deploy,
_delete
} from '@/api/northbound/alicloud';
import { query, _undeploy, _deploy, _delete } from '@/api/northbound/alicloud';
import type { ActionsType } from '@/components/Table/index.vue';
import { getImage } from '@/utils/comm';
import { message } from 'ant-design-vue';
import { useMenuStore } from 'store/menu'
import { useMenuStore } from 'store/menu';
const router = useRouter();
const instanceRef = ref<Record<string, any>>({});
const params = ref<Record<string, any>>({});
const current = ref<Record<string, any>>({});
const menuStory = useMenuStore()
const menuStory = useMenuStore();
const statusMap = new Map();
statusMap.set('enabled', 'success');
@ -167,7 +159,7 @@ const columns = [
type: 'select',
options: [
{ label: '正常', value: 'enabled' },
{ label: '禁用', value: 'disabled' }
{ label: '禁用', value: 'disabled' },
],
},
},
@ -184,14 +176,14 @@ const columns = [
* 新增
*/
const handleAdd = () => {
menuStory.jumpPage('Northbound/AliCloud/Detail', { id: ':id'})
menuStory.jumpPage('Northbound/AliCloud/Detail', { id: ':id' });
};
/**
* 查看
*/
const handleView = (id: string) => {
menuStory.jumpPage('Northbound/AliCloud/Detail', { id }, { type: 'view'})
menuStory.jumpPage('Northbound/AliCloud/Detail', { id }, { type: 'view' });
};
const getActions = (
@ -219,7 +211,11 @@ const getActions = (
},
icon: 'EditOutlined',
onClick: () => {
menuStory.jumpPage('Northbound/AliCloud/Detail', { id: data.id }, { type: 'edit'})
menuStory.jumpPage(
'Northbound/AliCloud/Detail',
{ id: data.id },
{ type: 'edit' },
);
},
},
{
@ -283,6 +279,6 @@ const getActions = (
};
const handleSearch = (_params: any) => {
params.value = _params
}
params.value = _params;
};
</script>

View File

@ -28,7 +28,6 @@
<CardBox
:value="slotProps"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
:statusNames="{
@ -37,9 +36,7 @@
}"
>
<template #img>
<slot name="img">
<img :src="getImage('/cloud/dueros.png')" />
</slot>
<img :src="getImage('/cloud/dueros.png')" />
</template>
<template #content>
<h3

View File

@ -0,0 +1,3 @@
<template>
<div>AMap</div>
</template>

View File

@ -0,0 +1,3 @@
<template>
<div>Charts</div>
</template>

View File

@ -0,0 +1,143 @@
<template>
<div>
<a-table
:columns="columns"
size="small"
rowKey="id"
:dataSource="dataSource?.data"
@change="onChange"
:pagination="{
current: (dataSource?.pageIndex || 0) + 1,
pageSize: dataSource?.pageSize || 10,
showSizeChanger: true,
total: dataSource?.total || 0,
pageSizeOptions: [5, 10, 20, 50],
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'timestamp'">
{{ moment(record.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-button
v-if="
showLoad ||
(!getType(record?.value) &&
data?.valueType?.fileType === 'base64')
"
type="link"
@click="_download(record)"
><AIcon type="DownloadOutlined"
/></a-button>
<a-button type="link"
><AIcon type="SearchOutlined"
/></a-button>
</a-space>
</template>
</template>
</a-table>
</div>
</template>
<script lang="ts" setup>
import { getPropertyData } from '@/api/device/instance';
import { useInstanceStore } from '@/store/instance';
import encodeQuery from '@/utils/encodeQuery';
import moment from 'moment';
import { getType } from '../index';
const _props = defineProps({
data: {
type: Object,
default: () => {},
},
time: {
type: Array,
default: () => [],
},
});
const instanceStore = useInstanceStore();
const dataSource = ref({});
const columns = computed(() => {
const arr: any[] = [
{
title: '时间',
dataIndex: 'timestamp',
key: 'timestamp',
ellipsis: true,
},
{
title: _props.data?.name || '',
dataIndex: 'value',
key: 'value',
ellipsis: true,
},
];
if (_props.data?.valueType?.type != 'geoPoint') {
arr.push({
title: '操作',
dataIndex: 'action',
key: 'action',
});
}
return arr;
});
const showLoad = computed(() => {
return (
_props.data.valueType?.type === 'file' &&
_props.data?.valueType?.fileType === 'Binary(二进制)'
);
});
const queryPropertyData = async (params: any) => {
const resp = await getPropertyData(
instanceStore.current.id,
encodeQuery({
...params,
terms: {
property: _props.data.id,
timestamp$BTW: _props.time?.length ? _props.time : [],
},
sorts: { timestamp: 'desc' },
}),
);
if (resp.status === 200) {
dataSource.value = resp.result as any;
}
};
watchEffect(() => {
if (_props.data.id) {
queryPropertyData({
pageSize: _props.data.valueType?.type === 'file' ? 5 : 10,
pageIndex: 0,
});
}
});
const onChange = (page: any) => {
queryPropertyData({
pageSize: page.pageSize,
pageIndex: Number(page.current) - 1 || 0,
});
};
const _download = (record: any) => {
const downNode = document.createElement('a');
downNode.download = `${instanceStore.current.name}-${
_props.data.name
}${moment(new Date().getTime()).format('YYYY-MM-DD-HH-mm-ss')}.txt`;
downNode.style.display = 'none';
//Blob
const blob = new Blob([record.value]);
downNode.href = URL.createObjectURL(blob);
//
document.body.appendChild(downNode);
downNode.click();
//
document.body.removeChild(downNode);
};
</script>

View File

@ -0,0 +1,81 @@
<template>
<a-space>
<a-radio-group
:value="radioValue"
button-style="solid"
@change="onRadioChange"
>
<a-radio-button value="today">今日</a-radio-button>
<a-radio-button value="week">近一周</a-radio-button>
<a-radio-button value="month">近一月</a-radio-button>
</a-radio-group>
<a-range-picker
show-time
v-model:value="dateValue"
:placeholder="['开始时间', '结束时间']"
@change="onRangeChange"
:allowClear="false"
/>
</a-space>
</template>
<script lang="ts" setup>
import dayjs from 'dayjs';
import type { Dayjs } from 'dayjs';
import { PropType } from 'vue';
type Props = [Dayjs, Dayjs] | undefined
const props = defineProps({
modelValue: {
type: Object as PropType<Props>,
default: undefined
},
});
type Emits = {
(e: 'update:modelValue', data: Props): void;
};
const emit = defineEmits<Emits>();
const radioValue = ref<string>('today');
const dateValue = ref<Props>();
const onRangeChange = (value: Props) => {
emit('update:modelValue', value);
radioValue.value = '';
}
const getTime = (type: string): Props => {
let st: number = 0;
const et = new Date().getTime();
if (type === 'today') {
st = dayjs().startOf('day').valueOf();
} else if (type === 'week') {
st = dayjs().subtract(6, 'days').valueOf();
} else if (type === 'month') {
st = dayjs().subtract(29, 'days').valueOf();
}
return [dayjs(st), dayjs(et)]
}
const onRadioChange = (e: any) => {
const value: string = e.target.value;
radioValue.value = value;
emit('update:modelValue', getTime(value));
};
onMounted(() => {
radioValue.value = 'today'
emit('update:modelValue', getTime('today'));
})
watch(
() => props.modelValue,
(newVal: Props) => {
dateValue.value = newVal
},
{ immediate: true, deep: true },
);
</script>

View File

@ -0,0 +1,55 @@
<template>
<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-tab-pane key="table" tab="列表">
<Table :data="props.data" :time="_getTimes" />
</a-tab-pane>
<a-tab-pane key="charts" tab="图表">
<Charts />
</a-tab-pane>
<a-tab-pane key="geo" tab="轨迹">
<AMap />
</a-tab-pane>
</a-tabs>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import type { Dayjs } from 'dayjs';
import TimeComponent from './TimeComponent.vue'
import Charts from './Charts.vue'
import AMap from './AMap.vue'
import Table from './Table.vue'
const props = defineProps({
data: {
type: Object,
default: () => {}
}
})
const _emits = defineEmits(['close'])
const activeKey = ref<'table' | 'charts' | 'geo'>('table')
const dateValue = ref<[Dayjs, Dayjs]>();
const _getTimes = computed(() => {
if(dateValue.value){
return [dateValue.value[0].valueOf(), dateValue.value[1].valueOf()]
}
return []
})
const onCancel = () => {
_emits('close')
}
</script>
<style lang="less" scoped>
</style>

View File

@ -53,6 +53,7 @@
import { getImage } from "@/utils/comm";
import { message } from "ant-design-vue";
import ValueDetail from './ValueDetail.vue'
import {getType, imgMap} from './index'
const _data = defineProps({
data: {
@ -73,47 +74,12 @@ const valueClass = computed(() => {
return _data.type === 'card' ? 'cardValue' : 'otherValue'
})
const imgMap = new Map<any, any>();
imgMap.set('txt', getImage('/running/txt.png'));
imgMap.set('doc', getImage('/running/doc.png'));
imgMap.set('xls', getImage('/running/xls.png'));
imgMap.set('ppt', getImage('/running/ppt.png'));
imgMap.set('docx', getImage('/running/docx.png'));
imgMap.set('xlsx', getImage('/running/xlsx.png'));
imgMap.set('pptx', getImage('/running/pptx.png'));
imgMap.set('pdf', getImage('/running/pdf.png'));
imgMap.set('img', getImage('/running/img.png'));
imgMap.set('error', getImage('/running/error.png'));
imgMap.set('video', getImage('/running/video.png'));
imgMap.set('other', getImage('/running/other.png'));
imgMap.set('obj', getImage('/running/obj.png'));
const imgList = ['.jpg', '.png', '.swf', '.tiff'];
const videoList = ['.m3u8', '.flv', '.mp4', '.rmvb', '.mvb'];
const fileList = ['.txt', '.doc', '.xls', '.pdf', '.ppt', '.docx', '.xlsx', '.pptx'];
const isHttps = document.location.protocol === 'https:';
const _types = ref<string>('')
const visible = ref<boolean>(false)
const temp = ref<boolean>(false)
const getType = (url: string) => {
let t: string = '';
[...imgList, ...videoList, ...fileList].map((item) => {
const str = item.slice(1, item.length);
if (url && String(url).indexOf(str) !== -1) {
if (imgList.includes(item)) {
t = 'img';
} else if (videoList.includes(item)) {
t = 'video';
} else {
t = str;
}
}
});
return t;
};
const onError = (e: any) => {
e.target.src = imgMap.get('other')

View File

@ -0,0 +1,37 @@
import { getImage } from "@/utils/comm";
export const imgMap = new Map<any, any>();
imgMap.set('txt', getImage('/running/txt.png'));
imgMap.set('doc', getImage('/running/doc.png'));
imgMap.set('xls', getImage('/running/xls.png'));
imgMap.set('ppt', getImage('/running/ppt.png'));
imgMap.set('docx', getImage('/running/docx.png'));
imgMap.set('xlsx', getImage('/running/xlsx.png'));
imgMap.set('pptx', getImage('/running/pptx.png'));
imgMap.set('pdf', getImage('/running/pdf.png'));
imgMap.set('img', getImage('/running/img.png'));
imgMap.set('error', getImage('/running/error.png'));
imgMap.set('video', getImage('/running/video.png'));
imgMap.set('other', getImage('/running/other.png'));
imgMap.set('obj', getImage('/running/obj.png'));
export const imgList = ['.jpg', '.png', '.swf', '.tiff'];
export const videoList = ['.m3u8', '.flv', '.mp4', '.rmvb', '.mvb'];
export const fileList = ['.txt', '.doc', '.xls', '.pdf', '.ppt', '.docx', '.xlsx', '.pptx'];
export const getType = (url: string) => {
let t: string = '';
[...imgList, ...videoList, ...fileList].map((item) => {
const str = item.slice(1, item.length);
if (url && String(url).indexOf(str) !== -1) {
if (imgList.includes(item)) {
t = 'img';
} else if (videoList.includes(item)) {
t = 'video';
} else {
t = str;
}
}
});
return t;
};

View File

@ -76,14 +76,16 @@
@close="indicatorVisible = false"
:data="currentInfo"
/>
<Detail v-if="detailVisible" :data="currentInfo" @close="detailVisible = false" />
</template>
<script lang="ts" setup>
import _, { groupBy, throttle, toArray } from 'lodash-es';
import _, { groupBy, toArray } from 'lodash-es';
import { PropertyData } from '../../../typings';
import PropertyCard from './PropertyCard.vue';
import ValueRender from './ValueRender.vue';
import Save from './Save.vue';
import Detail from './Detail/index.vue';
import Indicators from './Indicators.vue';
import { getProperty } from '@/api/device/instance';
import { useInstanceStore } from '@/store/instance';

View File

@ -12,6 +12,7 @@
<a-tabs
tab-position="left"
style="height: 600px"
v-if="tabList.length"
v-model:activeKey="activeKey"
:tabBarStyle="{ width: '200px' }"
@change="tabChange"
@ -22,6 +23,7 @@
:tab="i.tab"
/>
</a-tabs>
<JEmpty v-else style="margin: 250px 0" />
</div>
<div class="property-box-right">
<Event v-if="type === 'event'" :data="data" />

View File

@ -61,14 +61,15 @@
showSearch
v-model:value="modelRef.productId"
placeholder="请选择所属产品"
:filter-option="filterOption"
>
<a-select-option
:value="item.id"
v-for="item in productList"
:key="item.id"
:title="item.name"
:label="item.name"
:disabled="!!props.data.id"
></a-select-option>
>{{item.name}}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="说明" name="describe">
@ -110,6 +111,10 @@ const modelRef = reactive({
photoUrl: getImage('/device/instance/device-card.png'),
});
const filterOption = (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
const vailId = async (_: Record<string, any>, value: string) => {
if (!props?.data?.id && value) {
const resp = await isExists(value);

View File

@ -140,7 +140,6 @@
:value="slotProps"
@click="handleClick"
:actions="getActions(slotProps, 'card')"
v-bind="slotProps"
:active="_selectedRowKeys.includes(slotProps.id)"
:status="slotProps.state?.value"
:statusText="slotProps.state?.text"
@ -151,13 +150,9 @@
}"
>
<template #img>
<slot name="img">
<img
:src="
getImage('/device/instance/device-card.png')
"
/>
</slot>
<img
:src="getImage('/device/instance/device-card.png')"
/>
</template>
<template #content>
<h3
@ -292,7 +287,7 @@ const operationVisible = ref<boolean>(false);
const api = ref<string>('');
const type = ref<string>('');
const menuStory = useMenuStore()
const menuStory = useMenuStore();
const statusMap = new Map();
statusMap.set('online', 'success');
@ -538,7 +533,7 @@ const handleAdd = () => {
* 查看
*/
const handleView = (id: string) => {
menuStory.jumpPage('device/Instance/Detail', {id})
menuStory.jumpPage('device/Instance/Detail', { id });
};
const getActions = (