feat: 运行状态
This commit is contained in:
parent
1eda294d48
commit
68eaa0ac76
|
@ -114,6 +114,14 @@ export const isExists = (id: string) => server.get(`/device-instance/${id}/exist
|
|||
*/
|
||||
export const update = (data: Partial<DeviceInstance>) => data.id ? server.patch(`/device-instance`, data) : server.post(`/device-instance`, data)
|
||||
|
||||
/**
|
||||
* 修改设备信息
|
||||
* @param id 设备id
|
||||
* @param data 设备信息
|
||||
* @returns
|
||||
*/
|
||||
export const modify = (id: string, data: Partial<DeviceInstance>) => server.put(`/device-instance/${id}`, data)
|
||||
|
||||
/**
|
||||
* 获取配置信息
|
||||
* @param id 设备id
|
||||
|
|
|
@ -45,6 +45,7 @@ const iconKeys = [
|
|||
'InfoCircleOutlined',
|
||||
'SearchOutlined',
|
||||
'EllipsisOutlined',
|
||||
'ClockCircleOutlined'
|
||||
]
|
||||
|
||||
const Icon = (props: {type: string}) => {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
.jtable-body {
|
||||
width: 100%;
|
||||
padding: 0 24px 24px;
|
||||
padding: 16px 24px 24px;
|
||||
background-color: white;
|
||||
.jtable-body-header {
|
||||
padding: 16px 0;
|
||||
padding-bottom: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
|
|
@ -40,11 +40,11 @@ export interface ActionsType {
|
|||
children?: ActionsType[];
|
||||
}
|
||||
|
||||
export interface JColumnProps extends ColumnProps{
|
||||
export interface JColumnProps extends ColumnProps {
|
||||
scopedSlots?: boolean; // 是否为插槽 true: 是 false: 否
|
||||
}
|
||||
|
||||
export interface JTableProps extends TableProps{
|
||||
export interface JTableProps extends TableProps {
|
||||
request?: (params?: Record<string, any>) => Promise<Partial<RequestData>>;
|
||||
cardBodyClass?: string;
|
||||
columns: JColumnProps[];
|
||||
|
@ -53,8 +53,8 @@ export interface JTableProps extends TableProps{
|
|||
// actions?: ActionsType[];
|
||||
noPagination?: boolean;
|
||||
rowSelection?: TableProps['rowSelection'];
|
||||
cardProps?: Record<string, any>;
|
||||
dataSource?: Record<string, any>[];
|
||||
cardProps?: Record<string, any>;
|
||||
dataSource?: Record<string, any>[];
|
||||
gridColumn?: number;
|
||||
/**
|
||||
* 用于不同分辨率
|
||||
|
@ -62,10 +62,11 @@ export interface JTableProps extends TableProps{
|
|||
* gridColumns[1] 1440 ~ 1600 分辨率;
|
||||
* gridColumns[2] > 1600 分辨率;
|
||||
*/
|
||||
gridColumns?: number[];
|
||||
alertRender?: boolean;
|
||||
type?: keyof typeof TypeEnum;
|
||||
defaultParams?: Record<string, any>;
|
||||
gridColumns?: number[];
|
||||
alertRender?: boolean;
|
||||
type?: keyof typeof TypeEnum;
|
||||
defaultParams?: Record<string, any>;
|
||||
bodyStyle?: Record<string, any>;
|
||||
}
|
||||
|
||||
const JTable = defineComponent<JTableProps>({
|
||||
|
@ -88,13 +89,17 @@ const JTable = defineComponent<JTableProps>({
|
|||
type: String,
|
||||
default: ''
|
||||
},
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
default: () => { }
|
||||
},
|
||||
model: {
|
||||
type: [String, undefined],
|
||||
|
@ -142,7 +147,7 @@ const JTable = defineComponent<JTableProps>({
|
|||
}
|
||||
}
|
||||
} as any,
|
||||
setup(props: JTableProps ,{ slots, emit, expose }){
|
||||
setup(props: JTableProps, { slots, emit, expose }) {
|
||||
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE
|
||||
const _model = ref<keyof typeof ModelEnum>(props.model ? props.model : ModelEnum.CARD); // 模式切换
|
||||
const column = ref<number>(props.gridColumn || 4);
|
||||
|
@ -174,9 +179,9 @@ const JTable = defineComponent<JTableProps>({
|
|||
*/
|
||||
const handleSearch = async (_params?: Record<string, any>) => {
|
||||
loading.value = true
|
||||
if(props.request) {
|
||||
if (props.request) {
|
||||
const resp = await props.request({
|
||||
pageIndex: 0,
|
||||
pageIndex: 0,
|
||||
pageSize: 12,
|
||||
...props.defaultParams,
|
||||
..._params,
|
||||
|
@ -185,14 +190,14 @@ const JTable = defineComponent<JTableProps>({
|
|||
...(_params?.terms || [])
|
||||
]
|
||||
})
|
||||
if(resp.status === 200){
|
||||
if(props.type === 'PAGE'){
|
||||
if (resp.status === 200) {
|
||||
if (props.type === 'PAGE') {
|
||||
// 判断如果是最后一页且最后一页为空,就跳转到前一页
|
||||
if(resp.result.total && resp.result.pageSize && resp.result.pageIndex && resp.result?.data?.length === 0) {
|
||||
if (resp.result.total && resp.result.pageSize && resp.result.pageIndex && resp.result?.data?.length === 0) {
|
||||
handleSearch({
|
||||
..._params,
|
||||
pageSize: pageSize.value,
|
||||
pageIndex: pageIndex.value > 0 ? pageIndex.value - 1 : 0,
|
||||
pageIndex: pageIndex.value > 0 ? pageIndex.value - 1 : 0,
|
||||
})
|
||||
} else {
|
||||
_dataSource.value = resp.result?.data || []
|
||||
|
@ -204,31 +209,30 @@ const JTable = defineComponent<JTableProps>({
|
|||
_dataSource.value = resp?.result || []
|
||||
}
|
||||
} else {
|
||||
_dataSource.value = []
|
||||
_dataSource.value = []
|
||||
}
|
||||
} else {
|
||||
console.log(props?.dataSource)
|
||||
_dataSource.value = props?.dataSource || []
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.params,
|
||||
() => props.params,
|
||||
(newValue) => {
|
||||
handleSearch(newValue)
|
||||
},
|
||||
{deep: true, immediate: true}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.dataSource,
|
||||
(newValue) => {
|
||||
if(props.dataSource){
|
||||
() => props.dataSource,
|
||||
() => {
|
||||
if (props.dataSource && !props.request) {
|
||||
handleSearch(props.params)
|
||||
}
|
||||
},
|
||||
{deep: true, immediate: true}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -241,6 +245,10 @@ const JTable = defineComponent<JTableProps>({
|
|||
window.onresize = null
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
// console.log(props.bodyStyle)
|
||||
})
|
||||
|
||||
/**
|
||||
* 刷新数据
|
||||
* @param _params
|
||||
|
@ -256,10 +264,10 @@ const JTable = defineComponent<JTableProps>({
|
|||
/**
|
||||
* 导出方法
|
||||
*/
|
||||
expose({ reload })
|
||||
|
||||
expose({ reload })
|
||||
|
||||
return () => <Spin spinning={loading.value}>
|
||||
<div class={styles["jtable-body"]}>
|
||||
<div class={styles["jtable-body"]} style={{ ...props.bodyStyle }}>
|
||||
<div class={styles["jtable-body-header"]}>
|
||||
<div class={styles["jtable-body-header-left"]}>
|
||||
{/* 顶部左边插槽 */}
|
||||
|
@ -278,7 +286,7 @@ const JTable = defineComponent<JTableProps>({
|
|||
<div class={[styles["jtable-setting-item"], ModelEnum.TABLE === _model.value ? styles['active'] : '']} onClick={() => {
|
||||
_model.value = ModelEnum.TABLE
|
||||
}}>
|
||||
<UnorderedListOutlined />
|
||||
<UnorderedListOutlined />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -288,66 +296,66 @@ const JTable = defineComponent<JTableProps>({
|
|||
<div class={styles['jtable-content']}>
|
||||
{
|
||||
props.alertRender && props?.rowSelection && props?.rowSelection?.selectedRowKeys && props.rowSelection.selectedRowKeys?.length ?
|
||||
<div class={styles['jtable-alert']}>
|
||||
<Alert
|
||||
message={'已选择' + props?.rowSelection?.selectedRowKeys?.length + '项'}
|
||||
type="info"
|
||||
onClose={() => {
|
||||
emit('cancelSelect')
|
||||
}}
|
||||
closeText={<a-button type="link">取消选择</a-button>}
|
||||
/>
|
||||
</div> : null
|
||||
<div class={styles['jtable-alert']}>
|
||||
<Alert
|
||||
message={'已选择' + props?.rowSelection?.selectedRowKeys?.length + '项'}
|
||||
type="info"
|
||||
onClose={() => {
|
||||
emit('cancelSelect')
|
||||
}}
|
||||
closeText={<a-button type="link">取消选择</a-button>}
|
||||
/>
|
||||
</div> : null
|
||||
}
|
||||
{
|
||||
_model.value === ModelEnum.CARD ?
|
||||
<div class={styles['jtable-card']}>
|
||||
{
|
||||
_dataSource.value.length ?
|
||||
<div
|
||||
class={styles['jtable-card-items']}
|
||||
style={{gridTemplateColumns: `repeat(${column.value}, 1fr)`}}
|
||||
>
|
||||
{
|
||||
_dataSource.value.map(item => slots.card ?
|
||||
<div class={[styles['jtable-card-item'], props.cardBodyClass]}>
|
||||
{slots.card(item)}
|
||||
</div> : null
|
||||
)
|
||||
}
|
||||
</div> :
|
||||
<div><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>
|
||||
}
|
||||
</div> :
|
||||
<div>
|
||||
<Table
|
||||
dataSource={_dataSource.value}
|
||||
columns={_columns.value}
|
||||
pagination={false}
|
||||
rowKey="id"
|
||||
rowSelection={props.rowSelection}
|
||||
scroll={{x: 1366}}
|
||||
v-slots={{
|
||||
bodyCell: (dt: Record<string, any>) => {
|
||||
const {column, record} = dt;
|
||||
if((column?.key || column?.dataIndex) && column?.scopedSlots && (slots?.[column?.dataIndex] || slots?.[column?.key])) {
|
||||
const _key = column?.key || column?.dataIndex
|
||||
return slots?.[_key]!(record)
|
||||
} else {
|
||||
return record?.[column?.dataIndex] || ''
|
||||
<div class={styles['jtable-card']}>
|
||||
{
|
||||
_dataSource.value.length ?
|
||||
<div
|
||||
class={styles['jtable-card-items']}
|
||||
style={{ gridTemplateColumns: `repeat(${column.value}, 1fr)` }}
|
||||
>
|
||||
{
|
||||
_dataSource.value.map(item => slots.card ?
|
||||
<div class={[styles['jtable-card-item'], props.cardBodyClass]}>
|
||||
{slots.card(item)}
|
||||
</div> : null
|
||||
)
|
||||
}
|
||||
</div> :
|
||||
<div><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>
|
||||
}
|
||||
</div> :
|
||||
<div>
|
||||
<Table
|
||||
dataSource={_dataSource.value}
|
||||
columns={_columns.value}
|
||||
pagination={false}
|
||||
rowKey="id"
|
||||
rowSelection={props.rowSelection}
|
||||
scroll={{ x: 1366 }}
|
||||
v-slots={{
|
||||
bodyCell: (dt: Record<string, any>) => {
|
||||
const { column, record } = dt;
|
||||
if ((column?.key || column?.dataIndex) && column?.scopedSlots && (slots?.[column?.dataIndex] || slots?.[column?.key])) {
|
||||
const _key = column?.key || column?.dataIndex
|
||||
return slots?.[_key]!(record)
|
||||
} else {
|
||||
return record?.[column?.dataIndex] || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{/* 分页 */}
|
||||
{
|
||||
(!!_dataSource.value.length) && !props.noPagination && props.type === 'PAGE' &&
|
||||
<div class={styles['jtable-pagination']}>
|
||||
<Pagination
|
||||
size="small"
|
||||
<Pagination
|
||||
size="small"
|
||||
total={total.value}
|
||||
showQuickJumper={false}
|
||||
showSizeChanger={true}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
<template>
|
||||
<a-drawer placement="right" :closable="false" :visible="true">
|
||||
<template #title>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
"
|
||||
>
|
||||
<span
|
||||
><AIcon
|
||||
type="CloseOutlined"
|
||||
style="margin-right: 5px"
|
||||
@click="onClose"
|
||||
/>编辑配置</span
|
||||
>
|
||||
<a-button type="primary" @click="saveBtn">保存</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<a-form layout="vertical" ref="formRef" :model="modelRef">
|
||||
<template v-for="(item, index) in props.config" :key="index">
|
||||
<a-form-item
|
||||
:name="item.property"
|
||||
v-for="i in item.properties"
|
||||
:key="i.property"
|
||||
>
|
||||
<template #label>
|
||||
<span style="margin-right: 5px">{{ i.name }}</span>
|
||||
<a-tooltip v-if="i.description" :title="i.description"
|
||||
><AIcon type="QuestionCircleOutlined"
|
||||
/></a-tooltip>
|
||||
</template>
|
||||
<ValueItem
|
||||
v-model:modelValue="modelRef[i.property]"
|
||||
:itemType="i.type.type"
|
||||
:options="
|
||||
i.type.type === 'enum'
|
||||
? (i.type?.elements || []).map((item) => {
|
||||
return {
|
||||
label: item?.text,
|
||||
value: item?.value,
|
||||
};
|
||||
})
|
||||
: undefined
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { modify } from '@/api/device/instance';
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
const emit = defineEmits(['close', 'save']);
|
||||
|
||||
const formRef = ref();
|
||||
const modelRef = reactive({});
|
||||
|
||||
const instanceStore = useInstanceStore();
|
||||
|
||||
const props = defineProps({
|
||||
config: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
const obj = instanceStore.current?.configuration
|
||||
if(obj && Object.keys(obj).length) {
|
||||
(props?.config || []).map((item: any) => {
|
||||
if(Array.isArray(item.properties) && item?.properties.length){
|
||||
item.properties.map((i: any) => {
|
||||
modelRef[i.property] = obj[i.property]
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
const onClose = () => {
|
||||
emit('close');
|
||||
formRef.value.resetFields();
|
||||
};
|
||||
|
||||
const saveBtn = () => {
|
||||
formRef.value.validate().then(async () => {
|
||||
const values = toRaw(modelRef);
|
||||
const resp = await modify(instanceStore.current?.id || '', {
|
||||
id: instanceStore.current?.id,
|
||||
configuration: { ...values }
|
||||
})
|
||||
if(resp.status === 200){
|
||||
message.success('操作成功!')
|
||||
emit('save');
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div style="margin-top: 20px" v-if="config.length">
|
||||
<div style="display: flex;">
|
||||
<div style="display: flex; margin-bottom: 20px; align-items: center;">
|
||||
<div style="font-size: 16px; font-weight: 700">配置</div>
|
||||
<a-space>
|
||||
<a-button type="link" @click="visible = true"><AIcon type="EditOutlined" />编辑</a-button>
|
||||
|
@ -13,11 +13,11 @@
|
|||
</a-space>
|
||||
</div>
|
||||
<a-descriptions bordered size="small" v-for="i in config" :key="i.name">
|
||||
<template #title><h4>{{i.name}}</h4></template>
|
||||
<template #title><h4 style="font-size: 15px">{{i.name}}</h4></template>
|
||||
<a-descriptions-item v-for="item in i.properties" :key="item.property">
|
||||
<template #label>
|
||||
<span style="margin-right: 5px">{{item.name}}</span>
|
||||
<a-tooltip v-if="item.description" :title="item.description"><AIcon type="QuestionCircleOutlined" /></a-tooltip>
|
||||
<span>{{item.name}}</span>
|
||||
</template>
|
||||
<span v-if="item.type.type === 'password' && instanceStore.current?.configuration?.[item.property]?.length > 0">******</span>
|
||||
<span v-else>
|
||||
|
@ -26,6 +26,7 @@
|
|||
</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
<Save v-if="visible" @save="saveBtn" @close="visible = false" :config="config" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -34,6 +35,7 @@ import { useInstanceStore } from "@/store/instance"
|
|||
import { ConfigMetadata } from "@/views/device/Product/typings"
|
||||
import { getConfigMetadata, _deploy, configurationReset } from '@/api/device/instance'
|
||||
import { message } from "ant-design-vue"
|
||||
import Save from './Save.vue'
|
||||
|
||||
const instanceStore = useInstanceStore()
|
||||
const visible = ref<boolean>(false)
|
||||
|
@ -78,4 +80,11 @@ const resetBtn = async () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
const saveBtn = () => {
|
||||
visible.value = false
|
||||
if(instanceStore.current.id){
|
||||
instanceStore.refresh(instanceStore.current.id)
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<Search :columns="columns" target="device-instance-running-events" />
|
||||
<JTable
|
||||
ref="eventsRef"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
model="TABLE"
|
||||
:bodyStyle="{padding: '0 24px'}"
|
||||
>
|
||||
<template #timestamp="slotProps">
|
||||
{{ moment(slotProps.timestamp).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-button type="link" @click="detail(slotProps)">
|
||||
<AIcon type="SearchOutlined" />
|
||||
</a-button>
|
||||
</template>
|
||||
</JTable>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import moment from 'moment'
|
||||
const events = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
|
||||
const columns = ref<Record<string, any>>([
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
}
|
||||
])
|
||||
|
||||
const dataSource = ref<Record<string, any>[]>([])
|
||||
|
||||
watchEffect(() => {
|
||||
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`,
|
||||
// renderText: (text) => (typeof text === 'object' ? JSON.stringify(text) : text),
|
||||
})
|
||||
})
|
||||
} else {
|
||||
columns.value.splice(0, 0, {
|
||||
title: '数据',
|
||||
dataIndex: 'value',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const detail = () => {
|
||||
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,114 @@
|
|||
<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>
|
||||
<a-tooltip title="设置属性至设备" v-if="data.expands?.type?.includes('write')">
|
||||
<AIcon
|
||||
type="EditOutlined"
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="指标" v-if="(data.expands?.metrics || []).length > 0 &&
|
||||
['int', 'long', 'float', 'double', 'string', 'boolean', 'date'].includes(
|
||||
data.valueType?.type || '',
|
||||
)">
|
||||
<AIcon
|
||||
type="ClockCircleOutlined"
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="获取最新属性值" v-if="data.expands?.type?.includes('read')">
|
||||
<AIcon
|
||||
type="SyncOutlined"
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="详情">
|
||||
<AIcon
|
||||
type="BarsOutlined"
|
||||
style="font-size: 12px"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value">
|
||||
<ValueRender :data="data" />
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div style="color: rgba(0, 0, 0, .65); font-size: 12px">更新时间</div>
|
||||
<div class="time-value">{{data?.time || '--'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ValueRender from './ValueRender.vue'
|
||||
const _props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
const loading = ref<boolean>(true);
|
||||
|
||||
watchEffect(() => {
|
||||
if (_props.data.name) {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.card-box {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
width: 100%;
|
||||
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 154px;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.title {
|
||||
width: 60%;
|
||||
margin-right: 10px;
|
||||
overflow: hidden;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
color: #323130;
|
||||
font-weight: 700;
|
||||
font-size: 24px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
.time-value {
|
||||
margin-top: 5px;
|
||||
font-size: 16px;
|
||||
min-height: 25px;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div>
|
||||
{{data.value || '--'}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const _data = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,166 @@
|
|||
<template>
|
||||
<JTable
|
||||
ref="metadataRef"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:bodyStyle="{padding: 0}"
|
||||
>
|
||||
<template #headerTitle>
|
||||
<a-input-search
|
||||
placeholder="请输入名称"
|
||||
style="width: 300px; margin-bottom: 10px"
|
||||
@search="onSearch"
|
||||
/>
|
||||
</template>
|
||||
<template #card="slotProps">
|
||||
<PropertyCard :data="slotProps" />
|
||||
</template>
|
||||
<template #value="slotProps">
|
||||
<ValueRender :data="slotProps" />
|
||||
</template>
|
||||
<template #time="slotProps">
|
||||
{{slotProps.time || '--'}}
|
||||
</template>
|
||||
<template #action="slotProps">
|
||||
<a-space :size="16">
|
||||
<a-tooltip
|
||||
v-for="i in getActions(slotProps)"
|
||||
: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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropertyData } from "../../../typings"
|
||||
import PropertyCard from './PropertyCard.vue'
|
||||
import ValueRender from './ValueRender.vue'
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '值',
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
scopedSlots: true
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'time',
|
||||
key: 'time',
|
||||
scopedSlots: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
scopedSlots: true,
|
||||
},
|
||||
]
|
||||
|
||||
const _data = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const dataSource = ref<PropertyData[]>([])
|
||||
|
||||
const getActions = (data: Partial<Record<string, any>>) => {
|
||||
const arr = []
|
||||
if(data.expands?.type?.includes('write')){
|
||||
arr.push({
|
||||
key: 'edit',
|
||||
tooltip: {
|
||||
title: '设置属性至设备',
|
||||
},
|
||||
icon: 'EditOutlined',
|
||||
onClick: () => {
|
||||
|
||||
},
|
||||
})
|
||||
}
|
||||
if((data.expands?.metrics || []).length && ['int', 'long', 'float', 'double', 'string', 'boolean', 'date'].includes(
|
||||
data.valueType?.type || '',
|
||||
)){
|
||||
arr.push({
|
||||
key: 'metrics',
|
||||
tooltip: {
|
||||
title: '指标',
|
||||
},
|
||||
icon: 'ClockCircleOutlined',
|
||||
onClick: () => {
|
||||
|
||||
},
|
||||
})
|
||||
}
|
||||
if(data.expands?.type?.includes('read')){
|
||||
arr.push({
|
||||
key: 'read',
|
||||
tooltip: {
|
||||
title: '获取最新属性值',
|
||||
},
|
||||
icon: 'SyncOutlined',
|
||||
onClick: () => {
|
||||
|
||||
},
|
||||
})
|
||||
}
|
||||
arr.push({
|
||||
key: 'detail',
|
||||
text: '详情',
|
||||
tooltip: {
|
||||
title: '详情',
|
||||
},
|
||||
icon: 'BarsOutlined',
|
||||
onClick: () => {
|
||||
|
||||
},
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
dataSource.value = _data.data as PropertyData[]
|
||||
})
|
||||
|
||||
const onSearch = () => {};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
</style>
|
|
@ -1,46 +1,120 @@
|
|||
<template>
|
||||
<a-card>
|
||||
<a-row type="flex">
|
||||
<a-col flex="200px">
|
||||
<div>
|
||||
<a-input-search
|
||||
v-model:value="value"
|
||||
placeholder="请输入事件名称"
|
||||
style="width: 200px; margin-bottom: 10px"
|
||||
@search="onSearch"
|
||||
<div class="property-box">
|
||||
<div class="property-box-left">
|
||||
<a-input-search
|
||||
v-model:value="value"
|
||||
placeholder="请输入事件名称"
|
||||
style="width: 200px; margin-bottom: 10px"
|
||||
@search="onSearch"
|
||||
:allowClear="true"
|
||||
/>
|
||||
<a-tabs
|
||||
tab-position="left"
|
||||
style="height: 600px"
|
||||
v-model:activeKey="activeKey"
|
||||
:tabBarStyle="{ width: '200px' }"
|
||||
@change="tabChange"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="i in tabList"
|
||||
:key="i.key"
|
||||
:tab="i.tab"
|
||||
/>
|
||||
<a-tabs
|
||||
tab-position="left"
|
||||
:style="{ height: '600px' }"
|
||||
v-model:activeKey="activeKey"
|
||||
tabBarStyle="width: 200px"
|
||||
>
|
||||
<a-tab-pane v-for="i in tabList" :key="i.key" :tab="i.tab" />
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col flex="auto">
|
||||
<!-- <component :is="tabs[activeKey]" /> -->
|
||||
123
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<div class="property-box-right">
|
||||
<Event v-if="type === 'event'" :data="data" />
|
||||
<Property v-else :data="properties" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const activeKey = ref<string>('property')
|
||||
const tabList = ref<{key: string, tab: string}[]>([
|
||||
{
|
||||
key: 'property',
|
||||
tab: '属性'
|
||||
},
|
||||
{
|
||||
key: 'event1',
|
||||
tab: '事件1'
|
||||
import { useInstanceStore } from '@/store/instance';
|
||||
import _ from 'lodash';
|
||||
import Event from './Event/index.vue';
|
||||
import Property from './Property/index.vue';
|
||||
|
||||
const activeKey = ref<string>('property');
|
||||
const tabList = ref<{ key: string; tab: string; type: 'property' | 'event' }[]>(
|
||||
[
|
||||
{
|
||||
key: 'property',
|
||||
tab: '属性',
|
||||
type: 'property',
|
||||
},
|
||||
],
|
||||
);
|
||||
const type = ref<string>('property');
|
||||
const data = ref<Record<string, any>>({})
|
||||
const value = ref<string>('');
|
||||
const instanceStore = useInstanceStore()
|
||||
const metadata = JSON.parse(instanceStore.current?.metadata || '{}')
|
||||
const properties = metadata.properties
|
||||
const events = metadata.events
|
||||
|
||||
watch(() => events, (newVal) => {
|
||||
if(events && newVal.length){
|
||||
newVal.map((item: any) => {
|
||||
tabList.value.push({
|
||||
...item,
|
||||
key: item.id,
|
||||
tab: item.name,
|
||||
type: 'event',
|
||||
})
|
||||
})
|
||||
}
|
||||
])
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
|
||||
const onSearch = () => {
|
||||
const arr = [
|
||||
{
|
||||
key: 'property',
|
||||
tab: '属性',
|
||||
type: 'property',
|
||||
},
|
||||
...events.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
key: item.id,
|
||||
tab: item.name,
|
||||
type: 'event',
|
||||
}
|
||||
})
|
||||
]
|
||||
if(value.value){
|
||||
const li = arr.filter((i: any) => {
|
||||
return i?.tab.indexOf(value.value) !== -1;
|
||||
})
|
||||
tabList.value = _.cloneDeep(li)
|
||||
} else {
|
||||
tabList.value = _.cloneDeep(arr)
|
||||
}
|
||||
};
|
||||
const tabChange = (key: string) => {
|
||||
const dt = tabList.value.find((i) => i.key === key);
|
||||
if (dt) {
|
||||
data.value = dt
|
||||
type.value = dt.type;
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.property-box {
|
||||
display: flex;
|
||||
.property-box-left {
|
||||
width: 200px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.property-box-right {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</style>
|
Loading…
Reference in New Issue